# MDView ? Просмотрщик Markdown для Sprinter **MDView** ? программа для просмотра документов в формате *Markdown* на компьютере Sprinter (процессор Z80). Документ хранится в отдельном W3 окне и не занимает основную RAM программы. ## Возможности - Документы до **128 КБ** (8 страниц EMM по 16 КБ каждая) - До **16 384** экранных строк в индексе - Автоматический перенос слов по ширине экрана (80 столбцов) - Горизонтальный сдвиг для широких строк (блоки кода, таблицы) - Статус-бар: имя файла, диапазон строк, процент прокрутки - Спиннер в строке состояния во время загрузки и индексации - Поддержка ?мягкого? склеивания строк в абзацах и цитатах ## Запуск ``` mdview [имя_файла.md] ``` Если имя файла не задано, загружается `README.MD`. ## Управление ``` Клавиша Действие ───────────── ──────────────────────────────────────── Up Down Прокрутка на одну строку вверх / вниз PgUp PgDn Прокрутка на страницу (30 строк) Home Начало документа End Конец документа Left Right Горизонтальный сдвиг (только nowrap-строки) F1 Окно справки F8 Кодировка: CP866 ? CP1251 ? KOI8-R ? UTF-8 ? ... F10 / Esc Выход из программы ``` Кодировка определяется автоматически при открытии (BOM + эвристика по первым 4 КБ); `F8` переключает её вручную, если детекция ошиблась. 8-битные кодировки (CP866/CP1251/KOI8-R) переключаются мгновенно (ремап на отрисовке). Второй набор индекс/кэша (например UTF-8) строится лениво ? при первом переключении в него (короткая пауза со спиннером), дальше мгновенно. ## Синтаксис Markdown ### Заголовки Поддерживаются уровни H1?H4. Уровни H5 и H6 отображаются как H4. # Заголовок первого уровня ## Заголовок второго уровня ### Заголовок третьего уровня #### Заголовок четвёртого уровня ### Текстовое форматирование **Жирный текст** выделяется двойными звёздочками: `**текст**` *Курсив* выделяется одиночными звёздочками `*текст*` или знаком подчёркивания `_текст_` `Встроенный код` обозначается обратными кавычками ~~Зачёркнутый текст~~ ? двойные тильды: `~~текст~~` ### Ненумерованный список Маркеры `-`, `*` или `+`: - Первый пункт списка - Второй пункт списка - Третий пункт с достаточно длинным текстом, который при необходимости будет перенесён на следующую строку с сохранением отступа ### Нумерованный список 1. Первый элемент 2. Второй элемент 3. Третий элемент ### Цитата > Блок цитаты начинается с символа `>`. Несколько последовательных > строк одной цитаты склеиваются в единый абзац с автоматическим > переносом слов. ### Блок кода (verbatim) Блок кода заключается в тройные обратные кавычки. Внутри блока текст отображается ?как есть? без разбора Markdown: ``` #include #include int main(void) { puts("Hello, Sprinter!"); return 0; } ``` ### Горизонтальная линия Три или более символов `---`, `***` или `___` на отдельной строке: --- ## Технические характеристики - **Платформа:** Sprinter, процессор Z80 @ 21 МГц - **Кодировки:** CP866 / CP1251 / KOI8-R / UTF-8 (автоопределение, `F8`) - **Максимальный размер файла:** 128 КБ - **Максимальное число строк в индексе:** 16 384 - **Максимальная длина строки в рендер-кэше:** 255 ячеек (см. ограничение ниже) - **Режим памяти:** small - Код программы, cтек, данные, куча ? окнa W1-W2 (32 КБ, адреса 0x4000?0xBFFF). - Буфер файла ? страницы EMM, отображаемые в W3 (0xC000?0xFFFF) ## Известные ограничения ### Длина строки в MD-режиме ? 255 ячеек Рендер-кэш хранит каждую логическую строку как **не более 255 пар (символ, атрибут)** ? константа `MAX_CACHE_LINE_LEN`. Лимит задан типами: `g_ncells` и `cache_rec_t.len` ? `uint8_t`. Касается всех строк, но заметнее всего на **горизонтально скроллируемых** строках (блоки кода и строки таблиц, флаг `IF_HSCROLL`), которые в MD-режиме можно листать вправо. **Что происходит с более длинной строкой:** при индексации `gc_put()` молча отбрасывает каждую ячейку после 255-й (`if (g_ncells < MAX_CACHE_LINE_LEN)`). В кэш попадают только первые 255 ячеек, остаток **теряется** ? до него нельзя доскроллить и **нет маркера обрезки** на 255-й позиции (маркер `>` означает лишь ?есть ещё в пределах кэша?). Переполнения буфера нет ? `gc_put` проверяет границу. Важно: ?255 ячеек? ? это **отрендеренная ширина**, не байты исходника. Табы в коде разворачиваются в пробелы (до `TAB_STOP`), а ячейки таблицы добиваются пробелами до ширины колонки + рамки `│` ? поэтому кап достигается раньше, чем 255 ?полезных? символов. > **RAW-режим (`F2`) этого лимита не имеет** ? он рисует прямо из файла > побайтово, длинные строки видны целиком (через wrap `F3` или гориз. скролл). **Идея снятия лимита** (оценка, не реализовано) ? расширить длину до `uint16_t`: - `cache_rec_t.len` `uint8_t?uint16_t` ? структура остаётся **ровно 8 байт** (len съедает один pad-байт), адресация `idx<<3` не меняется. Бесплатно. - `g_ncells` / `g_ncells_at_space` ? `uint16_t` ? главная цена по **коду/скорости**: 16-битная арифметика на Z80 в горячем `gc_put` (вызов на каждую ячейку) и в scan-циклах. Ориентир: **+0.2?0.4 КБ кода** + замедление индексации. - Буфер `g_cells[MAX_CACHE_LINE_LEN*2]` в near-RAM (W2) ? главная цена по **памяти**: `2 ? кап` байт. Сейчас 510 Б; кап 512 ? +0.5 КБ, 1024 ? +1.5 КБ. EMM-кэш контента (1 МБ) длинные строки тянет легко ? узкое место именно near. - `viewport_x` (и копия в `docset_t`) + потолок `max_vx` (сейчас 248) ? `uint16_t`, иначе хранить >255 можно, а доскроллить нельзя. Плюс `widths[]`/`ccx` в таблицах/коде. Реалистичный компромисс ? кап 512?1023: хватит почти всем листингам/таблицам, цена ~+0.3 КБ кода и +0.5?1.5 КБ near-RAM. **Дешёвая полумера без `uint16_t`** ? ставить честный маркер обрезки на 255-й позиции, чтобы потеря была видна. ## TODO 1. **Увеличение размера документов.** Снять лимит 128 КБ: Достаточно разрешить работать с большим кол-вом страниц памяти, пока оттестированно на работе с 8-мю страницами по 16Кб. Сделано: форматированные таблицы с рамкой; поддержка кодировок CP866 / CP1251 / KOI8-R / UTF-8 с автоопределением и переключением по `F8` (второй набор строится лениво, по первому переключению). 4. **Ускорение рендеринга.** Кэш строк экрана. Оптимизация цикла вывода символов через BIOS WRCHAR (пакетный вывод, DMA). --- *MDView v0.2 · (c) 2026 Петров А.Г.* --- # Sprinter C Compiler ? v1.0 C toolchain for **Sprinter** ? the Z80-based home computer by Peters Plus, running ESTEX DSS. Host: macOS / Linux. Target: `.EXE` files in SprintEXE format. Built on top of **SDCC 4.5** (vendored in `third_party/sdcc/`). This repository adds everything Sprinter-specific: crt0, linker integration, libc wrappers over ESTEX, banked-call trampolines, graphics & accelerator API, mouse driver wrappers, and the `mkexe` utility for producing SprintEXE images. ## What you get * **`bin/sprinter-cc`** ? one-line driver: `sprinter-cc -o foo.exe foo.c` * **Memory modes**: `tiny`, `small`, `big`, `huge`, `manual` ? see below. * **stdio + conio**: printf, puts, putchar, getchar, fopen/fread/..., cprintf, cputs, putch, textcolor/textbackground/textattr, gotoxy, kbhit/getch. * **Graphics**: 320?256?256 and 640?256?16 modes, accelerator-backed primitives (hline / vline / rect / fill_rect / line via Bresenham, plus clear), bitmap-font text in both modes via BIOS character generator. * **File I/O**: POSIX (`open`/`read`/`write`/`close`/`lseek`/`unlink`/`creat`), FILE\* streams (`fopen`/`fgets`/`fwrite`/...), directory listing (`ffirst`/`fnext`), `chdir`/`getcwd`/`mkdir`/`rmdir`, `stat`/`fstat`. * **Memory**: 32 KB heap (W2-resident), banking-aware page allocator (`mem_alloc_pages`/`bank_read`/`bank_write`), explicit memory modes for sub-16 KB programs. * **Mouse**: full Sprinter driver wrapper (14 functions including custom cursor bitmaps). * **Environment**: `getenv`/`putenv`/`sysenv` over ESTEX `$46`. * **Time**: `getdatetime`/`setdatetime` + POSIX `time`/`localtime`/`mktime`/`asctime`/`ctime`. * **Misc**: `errno`/`strerror`/`perror`, `atexit`, `setjmp`/`longjmp`, `sleep`, full argv parsing in crt0. ## Quick start ```sh git clone sprinter-c cd sprinter-c make sdcc # one-time: fetch SDCC 4.5 binary (~25 MB) make all # build mkexe + libsprinter.lib + 27 examples make floppy # pack everything into mame/v306/IMG/mc.img cd mame/v306 && ./run_mame.sh # boot Sprinter in MAME ``` Compile a single program: ```sh cat > hello.c < int main(void) { puts("Hello, Sprinter!"); return 0; } EOF bin/sprinter-cc -o hello.exe hello.c ``` That's it ? `hello.exe` is now a valid SprintEXE you can `RUN HELLO` from the ESTEX shell. ## Memory modes Sprinter's address space is four 16 KB windows (W0 / W1 / W2 / W3). DSS allocates pages by program size ? small programs get only one page. Pick a memory mode based on what your program needs: | Mode | Code lives in | Banking | Use when | Note | |---|---|---|---|---| | `tiny` (default) | W2 (0x8100+) | no | code+data < 14 KB | | | `small` | W1-W2 (0x4100+) | no | code+data < 30 KB | | | `big` | W2 + W1 banking | yes (W1) | tiny + extra code modules | | | `huge` | W1-W2 + W3 banking | yes (W3) | small + extra code modules | | | `manual` | user-specified | optional | special layouts | Not implemented | ```sh sprinter-cc --memory small -o big.exe bigprog.c sprinter-cc --memory huge -o app.exe main.c --bank 1=engine.c --bank 2=ai.c ``` Banked functions are declared with `__banked`: ```c void engine_tick(int dt) __banked; // lives in BANK1, automatically swapped ``` ## Examples (27 total) | Example | What it demonstrates | |---|---| | `hello` | Hello world with stdio + conio Turbo-C-style colors | | `argv` | argv parsing in crt0 | | `cat` | File I/O ? read & print TEST.TXT | | `seek` | 32-bit lseek over a 100 KB file | | `ls` | Directory listing via ffirst/fnext | | `filetest` | FILE\* streams (fopen/fread/...) | | `stattest` | `stat`/`fstat` on files and directories | | `errno` | errno / strerror / perror | | `mem_test` | Page allocator + bank\_read/bank\_write | | `malloc` | Heap stress test (200+ allocations) | | `banked` | Banked code in W3 (huge mode) | | `bankedbg` | Banked code in W1 (big mode) | | `banklocl` | Bank-local static data and BSS | | `mouse` | Mouse driver in text mode | | `gfx_mous` | Mouse with custom bitmap cursor in graphics mode | | `gfx_demo` | 320?256?256 lines / rects / accelerator | | `gfx_d16` | 640?256?16 same primitives | | `gfx_text` | Bitmap-font text on graphics screen | | `timedir` | Date/time + directory listing | | `ptime` | POSIX time API | | `openenv` | open() flags + environment vars | | `conio` | conio API smoke test | | `attrprob` | Probe Sprinter text attribute byte layout | | `strtest` | string.h test (from SDCC's z80.lib) | | `stdlib` | stdlib.h test (qsort / rand / strtol / etc.) | | `assrtest` | assert() | | `rt_test` | Runtime helpers (sleep, setjmp, atexit) | ## Headers Standard: * `` ? puts / printf / FILE\* + Sprinter-specific dec/hex helpers * `` ? atoi / atof / malloc / qsort / ... (from SDCC z80.lib) * `` / `` / `` ? from SDCC z80.lib * `` ? read / write / close / lseek / unlink * `` ? open / creat + O\_RDONLY / O\_CREAT / ... * `` ? errno + error names + strerror * `` ? stat / fstat * `` / `` ? from SDCC Sprinter-specific: * `` ? putch / cputs / cprintf, textcolor / textbackground / textattr, kbhit / getch, clrscr, gotoxy, wherex/y * `` ? gfx_init/done, palette, putpixel, hline/vline/rect/fill_rect/line, text ? for both 320 and 640 modes (gfx_\*16 variants) * `` ? full 14-function driver wrapper + mouse_cursor_t with bitmap support * `` ? chdir / getcwd / mkdir / rmdir / ffirst / fnext / ffblk * `` ? getdatetime / setdatetime + POSIX time / localtime / etc. * `` ? raw ports, ESTEX/BIOS function numbers, env API * `` ? exit / \_exit / atexit * `` ? mem\_alloc\_pages / mem\_free\_block / bank\_read / bank\_write * `` ? Solid-C compatibility layer (aliases + BOOL/WORD/uint types) ## Toolchain commands ```sh make all # build mkexe + lib + every example make floppy # repack mame/v306/IMG/mc.img with all .exe files make check # 17 mkexe unit-tests make clean # remove all build artefacts make sdcc # one-time: fetch SDCC 4.5 binary ``` ### sprinter-cc options ``` sprinter-cc -o foo.exe foo.c [more.c ...] [options] --memory MODE tiny | small | big | huge | manual (default: tiny) --memory-manual SPEC explicit placement (CODE=W1|W2,DATA=W1|W2|SAME,BANKED=W1|W3) --stack-size N bytes reserved for the stack (default ~1278) --crt0=TYPE default | minimal | banked | small --bank N=FILE.c compile FILE.c into bank N (repeatable, max 15) --debug enable runtime diagnostics (defines DEBUG_RT) -I PATH extra include path -L 0xADDR / -E / -S override load / entry / stack addresses -Wl FLAG pass FLAG to sdldz80 --mkexe FLAG pass FLAG to mkexe (e.g. --mkexe -p --mkexe 0 for bank padding) -v verbose ``` ## Status What works in v1.0: * Compile / link / pack to SprintEXE ? verified on all 27 examples * Four memory modes (tiny / small / big / huge) * Graphics (both modes) with accelerator * Mouse (text + graphics cursor) * File I/O, directories, environment, time * All headers listed above Deferred to v2.0 (see `docs/TODO.md`): * **Turbo-C-style BGI graphics API** ? `initgraph` / `setcolor` / `circle` / `getimage` / `putimage` / etc. on top of our `gfx_*` primitives * Remaining Solid-C compatibility gaps (Phase 2/3) ? see `docs/solid_c_compatibility.md` * Manual memory mode * Rewrite FILE\* stream API (current implementation is very primitive and doesn't use buffers) Deferred to v3.0: * **IM2 interrupt handlers** ? research complete (`docs/im2_isr_design.md`), implementation scheduled for v3 * **Audio API** (AY-3-8910 + COVOX) ? requires IM2 * **ISA-8 slot drivers** ? requires IM2 (???) ## Documentation * `docs/TODO.md` ? roadmap and open work items * `docs/solid_c_compatibility.md` ? gap analysis vs Solid-C 2004 * `docs/im2_isr_design.md` ? interrupt handler design (v2) * `docs/converted/` ? source documentation (ESTEX, BIOS, architecture) converted to plain text for `grep` * `docs/reference/`, `docs/samples/`, `docs/memory management/` ? original Russian docs and code samples from Peters Plus ## Repository layout ``` bin/sprinter-cc one-line compiler driver (bash) toolchain/mkexe/ host-side tool: .ihx -> .exe SprintEXE toolchain/check_banks.py post-link bank size enforcer runtime/ crt0 variants (default, minimal, small, banked) bank trampolines, heap, heap_top libc/include/ headers libc/io|stdio|mem|gfx/ C and asm sources for libsprinter.lib lib/ Makefile that archives libsprinter.lib via sdar examples/ 27 example programs mame/v306/ MAME binary + Sprinter ROM/HDD images + floppy script third_party/sdcc/ vendored SDCC 4.5 (fetched via `make sdcc`) third_party/solid-c/ reference: original Sprinter native C (for compat target) docs/ documentation ``` ## License This repository contains: * Original code in `bin/`, `toolchain/`, `runtime/`, `libc/`, `lib/`, `examples/` ? MIT-licensed. * `third_party/sdcc/` ? SDCC 4.5 under GPLv2 with linking exception (see `third_party/sdcc/COPYING.txt`) * `third_party/solid-c/` ? original Sprinter Solid C, used only as a reference ## Credits * **Sprinter / Peters Plus** ? Иван Мак, Дмитрий Паринов and the original team * **SDCC** ? for the underlying Z80 compiler * **MAME** ? for the Sprinter Sp2000 emulation --- For questions / patches: see CONTRIBUTING.md (TBD) or open an issue. --- # 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` - Полный `` (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:` строки) из вывода `sdcc`. Через `-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 ? количество строк/столбцов экономии. Применимо ко всем композитным примитивам. # MDView ? Просмотрщик Markdown для Sprinter **MDView** ? программа для просмотра документов в формате *Markdown* на компьютере Sprinter (процессор Z80). Документ хранится в отдельном W3 окне и не занимает основную RAM программы. ## Возможности - Документы до **128 КБ** (8 страниц EMM по 16 КБ каждая) - До **16 384** экранных строк в индексе - Автоматический перенос слов по ширине экрана (80 столбцов) - Горизонтальный сдвиг для широких строк (блоки кода, таблицы) - Статус-бар: имя файла, диапазон строк, процент прокрутки - Спиннер в строке состояния во время загрузки и индексации - Поддержка ?мягкого? склеивания строк в абзацах и цитатах ## Запуск ``` mdview [имя_файла.md] ``` Если имя файла не задано, загружается `README.MD`. ## Управление ``` Клавиша Действие ───────────── ──────────────────────────────────────── Up Down Прокрутка на одну строку вверх / вниз PgUp PgDn Прокрутка на страницу (30 строк) Home Начало документа End Конец документа Left Right Горизонтальный сдвиг (только nowrap-строки) F1 Окно справки F8 Кодировка: CP866 ? CP1251 ? KOI8-R ? UTF-8 ? ... F10 / Esc Выход из программы ``` Кодировка определяется автоматически при открытии (BOM + эвристика по первым 4 КБ); `F8` переключает её вручную, если детекция ошиблась. 8-битные кодировки (CP866/CP1251/KOI8-R) переключаются мгновенно (ремап на отрисовке). Второй набор индекс/кэша (например UTF-8) строится лениво ? при первом переключении в него (короткая пауза со спиннером), дальше мгновенно. ## Синтаксис Markdown ### Заголовки Поддерживаются уровни H1?H4. Уровни H5 и H6 отображаются как H4. # Заголовок первого уровня ## Заголовок второго уровня ### Заголовок третьего уровня #### Заголовок четвёртого уровня ### Текстовое форматирование **Жирный текст** выделяется двойными звёздочками: `**текст**` *Курсив* выделяется одиночными звёздочками `*текст*` или знаком подчёркивания `_текст_` `Встроенный код` обозначается обратными кавычками ~~Зачёркнутый текст~~ ? двойные тильды: `~~текст~~` ### Ненумерованный список Маркеры `-`, `*` или `+`: - Первый пункт списка - Второй пункт списка - Третий пункт с достаточно длинным текстом, который при необходимости будет перенесён на следующую строку с сохранением отступа ### Нумерованный список 1. Первый элемент 2. Второй элемент 3. Третий элемент ### Цитата > Блок цитаты начинается с символа `>`. Несколько последовательных > строк одной цитаты склеиваются в единый абзац с автоматическим > переносом слов. ### Блок кода (verbatim) Блок кода заключается в тройные обратные кавычки. Внутри блока текст отображается ?как есть? без разбора Markdown: ``` #include #include int main(void) { puts("Hello, Sprinter!"); return 0; } ``` ### Горизонтальная линия Три или более символов `---`, `***` или `___` на отдельной строке: --- ## Технические характеристики - **Платформа:** Sprinter, процессор Z80 @ 21 МГц - **Кодировки:** CP866 / CP1251 / KOI8-R / UTF-8 (автоопределение, `F8`) - **Максимальный размер файла:** 128 КБ - **Максимальное число строк в индексе:** 16 384 - **Режим памяти:** small - Код программы, cтек, данные, куча ? окнa W1-W2 (32 КБ, адреса 0x4000?0xBFFF). - Буфер файла ? страницы EMM, отображаемые в W3 (0xC000?0xFFFF) ## TODO 1. **Увеличение размера документов.** Снять лимит 128 КБ: Достаточно разрешить работать с большим кол-вом страниц памяти, пока оттестированно на работе с 8-мю страницами по 16Кб. Сделано: форматированные таблицы с рамкой; поддержка кодировок CP866 / CP1251 / KOI8-R / UTF-8 с автоопределением и переключением по `F8` (второй набор строится лениво, по первому переключению). 4. **Ускорение рендеринга.** Кэш строк экрана. Оптимизация цикла вывода символов через BIOS WRCHAR (пакетный вывод, DMA). --- *MDView v0.2 · (c) 2026 Петров А.Г.* --- # Sprinter C Compiler ? v1.0 C toolchain for **Sprinter** ? the Z80-based home computer by Peters Plus, running ESTEX DSS. Host: macOS / Linux. Target: `.EXE` files in SprintEXE format. Built on top of **SDCC 4.5** (vendored in `third_party/sdcc/`). This repository adds everything Sprinter-specific: crt0, linker integration, libc wrappers over ESTEX, banked-call trampolines, graphics & accelerator API, mouse driver wrappers, and the `mkexe` utility for producing SprintEXE images. ## What you get * **`bin/sprinter-cc`** ? one-line driver: `sprinter-cc -o foo.exe foo.c` * **Memory modes**: `tiny`, `small`, `big`, `huge`, `manual` ? see below. * **stdio + conio**: printf, puts, putchar, getchar, fopen/fread/..., cprintf, cputs, putch, textcolor/textbackground/textattr, gotoxy, kbhit/getch. * **Graphics**: 320?256?256 and 640?256?16 modes, accelerator-backed primitives (hline / vline / rect / fill_rect / line via Bresenham, plus clear), bitmap-font text in both modes via BIOS character generator. * **File I/O**: POSIX (`open`/`read`/`write`/`close`/`lseek`/`unlink`/`creat`), FILE\* streams (`fopen`/`fgets`/`fwrite`/...), directory listing (`ffirst`/`fnext`), `chdir`/`getcwd`/`mkdir`/`rmdir`, `stat`/`fstat`. * **Memory**: 32 KB heap (W2-resident), banking-aware page allocator (`mem_alloc_pages`/`bank_read`/`bank_write`), explicit memory modes for sub-16 KB programs. * **Mouse**: full Sprinter driver wrapper (14 functions including custom cursor bitmaps). * **Environment**: `getenv`/`putenv`/`sysenv` over ESTEX `$46`. * **Time**: `getdatetime`/`setdatetime` + POSIX `time`/`localtime`/`mktime`/`asctime`/`ctime`. * **Misc**: `errno`/`strerror`/`perror`, `atexit`, `setjmp`/`longjmp`, `sleep`, full argv parsing in crt0. ## Quick start ```sh git clone sprinter-c cd sprinter-c make sdcc # one-time: fetch SDCC 4.5 binary (~25 MB) make all # build mkexe + libsprinter.lib + 27 examples make floppy # pack everything into mame/v306/IMG/mc.img cd mame/v306 && ./run_mame.sh # boot Sprinter in MAME ``` Compile a single program: ```sh cat > hello.c < int main(void) { puts("Hello, Sprinter!"); return 0; } EOF bin/sprinter-cc -o hello.exe hello.c ``` That's it ? `hello.exe` is now a valid SprintEXE you can `RUN HELLO` from the ESTEX shell. ## Memory modes Sprinter's address space is four 16 KB windows (W0 / W1 / W2 / W3). DSS allocates pages by program size ? small programs get only one page. Pick a memory mode based on what your program needs: | Mode | Code lives in | Banking | Use when | Note | |---|---|---|---|---| | `tiny` (default) | W2 (0x8100+) | no | code+data < 14 KB | | | `small` | W1-W2 (0x4100+) | no | code+data < 30 KB | | | `big` | W2 + W1 banking | yes (W1) | tiny + extra code modules | | | `huge` | W1-W2 + W3 banking | yes (W3) | small + extra code modules | | | `manual` | user-specified | optional | special layouts | Not implemented | ```sh sprinter-cc --memory small -o big.exe bigprog.c sprinter-cc --memory huge -o app.exe main.c --bank 1=engine.c --bank 2=ai.c ``` Banked functions are declared with `__banked`: ```c void engine_tick(int dt) __banked; // lives in BANK1, automatically swapped ``` ## Examples (27 total) | Example | What it demonstrates | |---|---| | `hello` | Hello world with stdio + conio Turbo-C-style colors | | `argv` | argv parsing in crt0 | | `cat` | File I/O ? read & print TEST.TXT | | `seek` | 32-bit lseek over a 100 KB file | | `ls` | Directory listing via ffirst/fnext | | `filetest` | FILE\* streams (fopen/fread/...) | | `stattest` | `stat`/`fstat` on files and directories | | `errno` | errno / strerror / perror | | `mem_test` | Page allocator + bank\_read/bank\_write | | `malloc` | Heap stress test (200+ allocations) | | `banked` | Banked code in W3 (huge mode) | | `bankedbg` | Banked code in W1 (big mode) | | `banklocl` | Bank-local static data and BSS | | `mouse` | Mouse driver in text mode | | `gfx_mous` | Mouse with custom bitmap cursor in graphics mode | | `gfx_demo` | 320?256?256 lines / rects / accelerator | | `gfx_d16` | 640?256?16 same primitives | | `gfx_text` | Bitmap-font text on graphics screen | | `timedir` | Date/time + directory listing | | `ptime` | POSIX time API | | `openenv` | open() flags + environment vars | | `conio` | conio API smoke test | | `attrprob` | Probe Sprinter text attribute byte layout | | `strtest` | string.h test (from SDCC's z80.lib) | | `stdlib` | stdlib.h test (qsort / rand / strtol / etc.) | | `assrtest` | assert() | | `rt_test` | Runtime helpers (sleep, setjmp, atexit) | ## Headers Standard: * `` ? puts / printf / FILE\* + Sprinter-specific dec/hex helpers * `` ? atoi / atof / malloc / qsort / ... (from SDCC z80.lib) * `` / `` / `` ? from SDCC z80.lib * `` ? read / write / close / lseek / unlink * `` ? open / creat + O\_RDONLY / O\_CREAT / ... * `` ? errno + error names + strerror * `` ? stat / fstat * `` / `` ? from SDCC Sprinter-specific: * `` ? putch / cputs / cprintf, textcolor / textbackground / textattr, kbhit / getch, clrscr, gotoxy, wherex/y * `` ? gfx_init/done, palette, putpixel, hline/vline/rect/fill_rect/line, text ? for both 320 and 640 modes (gfx_\*16 variants) * `` ? full 14-function driver wrapper + mouse_cursor_t with bitmap support * `` ? chdir / getcwd / mkdir / rmdir / ffirst / fnext / ffblk * `` ? getdatetime / setdatetime + POSIX time / localtime / etc. * `` ? raw ports, ESTEX/BIOS function numbers, env API * `` ? exit / \_exit / atexit * `` ? mem\_alloc\_pages / mem\_free\_block / bank\_read / bank\_write * `` ? Solid-C compatibility layer (aliases + BOOL/WORD/uint types) ## Toolchain commands ```sh make all # build mkexe + lib + every example make floppy # repack mame/v306/IMG/mc.img with all .exe files make check # 17 mkexe unit-tests make clean # remove all build artefacts make sdcc # one-time: fetch SDCC 4.5 binary ``` ### sprinter-cc options ``` sprinter-cc -o foo.exe foo.c [more.c ...] [options] --memory MODE tiny | small | big | huge | manual (default: tiny) --memory-manual SPEC explicit placement (CODE=W1|W2,DATA=W1|W2|SAME,BANKED=W1|W3) --stack-size N bytes reserved for the stack (default ~1278) --crt0=TYPE default | minimal | banked | small --bank N=FILE.c compile FILE.c into bank N (repeatable, max 15) --debug enable runtime diagnostics (defines DEBUG_RT) -I PATH extra include path -L 0xADDR / -E / -S override load / entry / stack addresses -Wl FLAG pass FLAG to sdldz80 --mkexe FLAG pass FLAG to mkexe (e.g. --mkexe -p --mkexe 0 for bank padding) -v verbose ``` ## Status What works in v1.0: * Compile / link / pack to SprintEXE ? verified on all 27 examples * Four memory modes (tiny / small / big / huge) * Graphics (both modes) with accelerator * Mouse (text + graphics cursor) * File I/O, directories, environment, time * All headers listed above Deferred to v2.0 (see `docs/TODO.md`): * **Turbo-C-style BGI graphics API** ? `initgraph` / `setcolor` / `circle` / `getimage` / `putimage` / etc. on top of our `gfx_*` primitives * Remaining Solid-C compatibility gaps (Phase 2/3) ? see `docs/solid_c_compatibility.md` * Manual memory mode * Rewrite FILE\* stream API (current implementation is very primitive and doesn't use buffers) Deferred to v3.0: * **IM2 interrupt handlers** ? research complete (`docs/im2_isr_design.md`), implementation scheduled for v3 * **Audio API** (AY-3-8910 + COVOX) ? requires IM2 * **ISA-8 slot drivers** ? requires IM2 (???) ## Documentation * `docs/TODO.md` ? roadmap and open work items * `docs/solid_c_compatibility.md` ? gap analysis vs Solid-C 2004 * `docs/im2_isr_design.md` ? interrupt handler design (v2) * `docs/converted/` ? source documentation (ESTEX, BIOS, architecture) converted to plain text for `grep` * `docs/reference/`, `docs/samples/`, `docs/memory management/` ? original Russian docs and code samples from Peters Plus ## Repository layout ``` bin/sprinter-cc one-line compiler driver (bash) toolchain/mkexe/ host-side tool: .ihx -> .exe SprintEXE toolchain/check_banks.py post-link bank size enforcer runtime/ crt0 variants (default, minimal, small, banked) bank trampolines, heap, heap_top libc/include/ headers libc/io|stdio|mem|gfx/ C and asm sources for libsprinter.lib lib/ Makefile that archives libsprinter.lib via sdar examples/ 27 example programs mame/v306/ MAME binary + Sprinter ROM/HDD images + floppy script third_party/sdcc/ vendored SDCC 4.5 (fetched via `make sdcc`) third_party/solid-c/ reference: original Sprinter native C (for compat target) docs/ documentation ``` ## License This repository contains: * Original code in `bin/`, `toolchain/`, `runtime/`, `libc/`, `lib/`, `examples/` ? MIT-licensed. * `third_party/sdcc/` ? SDCC 4.5 under GPLv2 with linking exception (see `third_party/sdcc/COPYING.txt`) * `third_party/solid-c/` ? original Sprinter Solid C, used only as a reference ## Credits * **Sprinter / Peters Plus** ? Иван Мак, Дмитрий Паринов and the original team * **SDCC** ? for the underlying Z80 compiler * **MAME** ? for the Sprinter Sp2000 emulation --- For questions / patches: see CONTRIBUTING.md (TBD) or open an issue. --- # 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` - Полный `` (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:` строки) из вывода `sdcc`. Через `-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 ? количество строк/столбцов экономии. Применимо ко всем композитным примитивам. # MDView ? Просмотрщик Markdown для Sprinter **MDView** ? программа для просмотра документов в формате *Markdown* на компьютере Sprinter (процессор Z80). Документ хранится в отдельном W3 окне и не занимает основную RAM программы. ## Возможности - Документы до **128 КБ** (8 страниц EMM по 16 КБ каждая) - До **16 384** экранных строк в индексе - Автоматический перенос слов по ширине экрана (80 столбцов) - Горизонтальный сдвиг для широких строк (блоки кода, таблицы) - Статус-бар: имя файла, диапазон строк, процент прокрутки - Спиннер в строке состояния во время загрузки и индексации - Поддержка ?мягкого? склеивания строк в абзацах и цитатах ## Запуск ``` mdview [имя_файла.md] ``` Если имя файла не задано, загружается `README.MD`. ## Управление ``` Клавиша Действие ───────────── ──────────────────────────────────────── Up Down Прокрутка на одну строку вверх / вниз PgUp PgDn Прокрутка на страницу (30 строк) Home Начало документа End Конец документа Left Right Горизонтальный сдвиг (только nowrap-строки) F1 Окно справки F8 Кодировка: CP866 ? CP1251 ? KOI8-R ? UTF-8 ? ... F10 / Esc Выход из программы ``` Кодировка определяется автоматически при открытии (BOM + эвристика по первым 4 КБ); `F8` переключает её вручную, если детекция ошиблась. 8-битные кодировки (CP866/CP1251/KOI8-R) переключаются мгновенно (ремап на отрисовке). Второй набор индекс/кэша (например UTF-8) строится лениво ? при первом переключении в него (короткая пауза со спиннером), дальше мгновенно. ## Синтаксис Markdown ### Заголовки Поддерживаются уровни H1?H4. Уровни H5 и H6 отображаются как H4. # Заголовок первого уровня ## Заголовок второго уровня ### Заголовок третьего уровня #### Заголовок четвёртого уровня ### Текстовое форматирование **Жирный текст** выделяется двойными звёздочками: `**текст**` *Курсив* выделяется одиночными звёздочками `*текст*` или знаком подчёркивания `_текст_` `Встроенный код` обозначается обратными кавычками ~~Зачёркнутый текст~~ ? двойные тильды: `~~текст~~` ### Ненумерованный список Маркеры `-`, `*` или `+`: - Первый пункт списка - Второй пункт списка - Третий пункт с достаточно длинным текстом, который при необходимости будет перенесён на следующую строку с сохранением отступа ### Нумерованный список 1. Первый элемент 2. Второй элемент 3. Третий элемент ### Цитата > Блок цитаты начинается с символа `>`. Несколько последовательных > строк одной цитаты склеиваются в единый абзац с автоматическим > переносом слов. ### Блок кода (verbatim) Блок кода заключается в тройные обратные кавычки. Внутри блока текст отображается ?как есть? без разбора Markdown: ``` #include #include int main(void) { puts("Hello, Sprinter!"); return 0; } ``` ### Горизонтальная линия Три или более символов `---`, `***` или `___` на отдельной строке: --- ## Технические характеристики - **Платформа:** Sprinter, процессор Z80 @ 21 МГц - **Кодировки:** CP866 / CP1251 / KOI8-R / UTF-8 (автоопределение, `F8`) - **Максимальный размер файла:** 128 КБ - **Максимальное число строк в индексе:** 16 384 - **Максимальная длина строки в рендер-кэше:** 255 ячеек (см. ограничение ниже) - **Режим памяти:** small - Код программы, cтек, данные, куча ? окнa W1-W2 (32 КБ, адреса 0x4000?0xBFFF). - Буфер файла ? страницы EMM, отображаемые в W3 (0xC000?0xFFFF) ## Известные ограничения ### Длина строки в MD-режиме ? 255 ячеек Рендер-кэш хранит каждую логическую строку как **не более 255 пар (символ, атрибут)** ? константа `MAX_CACHE_LINE_LEN`. Лимит задан типами: `g_ncells` и `cache_rec_t.len` ? `uint8_t`. Касается всех строк, но заметнее всего на **горизонтально скроллируемых** строках (блоки кода и строки таблиц, флаг `IF_HSCROLL`), которые в MD-режиме можно листать вправо. **Что происходит с более длинной строкой:** при индексации `gc_put()` молча отбрасывает каждую ячейку после 255-й (`if (g_ncells < MAX_CACHE_LINE_LEN)`). В кэш попадают только первые 255 ячеек, остаток **теряется** ? до него нельзя доскроллить и **нет маркера обрезки** на 255-й позиции (маркер `>` означает лишь ?есть ещё в пределах кэша?). Переполнения буфера нет ? `gc_put` проверяет границу. Важно: ?255 ячеек? ? это **отрендеренная ширина**, не байты исходника. Табы в коде разворачиваются в пробелы (до `TAB_STOP`), а ячейки таблицы добиваются пробелами до ширины колонки + рамки `│` ? поэтому кап достигается раньше, чем 255 ?полезных? символов. > **RAW-режим (`F2`) этого лимита не имеет** ? он рисует прямо из файла > побайтово, длинные строки видны целиком (через wrap `F3` или гориз. скролл). **Идея снятия лимита** (оценка, не реализовано) ? расширить длину до `uint16_t`: - `cache_rec_t.len` `uint8_t?uint16_t` ? структура остаётся **ровно 8 байт** (len съедает один pad-байт), адресация `idx<<3` не меняется. Бесплатно. - `g_ncells` / `g_ncells_at_space` ? `uint16_t` ? главная цена по **коду/скорости**: 16-битная арифметика на Z80 в горячем `gc_put` (вызов на каждую ячейку) и в scan-циклах. Ориентир: **+0.2?0.4 КБ кода** + замедление индексации. - Буфер `g_cells[MAX_CACHE_LINE_LEN*2]` в near-RAM (W2) ? главная цена по **памяти**: `2 ? кап` байт. Сейчас 510 Б; кап 512 ? +0.5 КБ, 1024 ? +1.5 КБ. EMM-кэш контента (1 МБ) длинные строки тянет легко ? узкое место именно near. - `viewport_x` (и копия в `docset_t`) + потолок `max_vx` (сейчас 248) ? `uint16_t`, иначе хранить >255 можно, а доскроллить нельзя. Плюс `widths[]`/`ccx` в таблицах/коде. Реалистичный компромисс ? кап 512?1023: хватит почти всем листингам/таблицам, цена ~+0.3 КБ кода и +0.5?1.5 КБ near-RAM. **Дешёвая полумера без `uint16_t`** ? ставить честный маркер обрезки на 255-й позиции, чтобы потеря была видна. ## TODO 1. **Увеличение размера документов.** Снять лимит 128 КБ: Достаточно разрешить работать с большим кол-вом страниц памяти, пока оттестированно на работе с 8-мю страницами по 16Кб. Сделано: форматированные таблицы с рамкой; поддержка кодировок CP866 / CP1251 / KOI8-R / UTF-8 с автоопределением и переключением по `F8` (второй набор строится лениво, по первому переключению). 4. **Ускорение рендеринга.** Кэш строк экрана. Оптимизация цикла вывода символов через BIOS WRCHAR (пакетный вывод, DMA). --- *MDView v0.2 · (c) 2026 Петров А.Г.* --- # Sprinter C Compiler ? v1.0 C toolchain for **Sprinter** ? the Z80-based home computer by Peters Plus, running ESTEX DSS. Host: macOS / Linux. Target: `.EXE` files in SprintEXE format. Built on top of **SDCC 4.5** (vendored in `third_party/sdcc/`). This repository adds everything Sprinter-specific: crt0, linker integration, libc wrappers over ESTEX, banked-call trampolines, graphics & accelerator API, mouse driver wrappers, and the `mkexe` utility for producing SprintEXE images. ## What you get * **`bin/sprinter-cc`** ? one-line driver: `sprinter-cc -o foo.exe foo.c` * **Memory modes**: `tiny`, `small`, `big`, `huge`, `manual` ? see below. * **stdio + conio**: printf, puts, putchar, getchar, fopen/fread/..., cprintf, cputs, putch, textcolor/textbackground/textattr, gotoxy, kbhit/getch. * **Graphics**: 320?256?256 and 640?256?16 modes, accelerator-backed primitives (hline / vline / rect / fill_rect / line via Bresenham, plus clear), bitmap-font text in both modes via BIOS character generator. * **File I/O**: POSIX (`open`/`read`/`write`/`close`/`lseek`/`unlink`/`creat`), FILE\* streams (`fopen`/`fgets`/`fwrite`/...), directory listing (`ffirst`/`fnext`), `chdir`/`getcwd`/`mkdir`/`rmdir`, `stat`/`fstat`. * **Memory**: 32 KB heap (W2-resident), banking-aware page allocator (`mem_alloc_pages`/`bank_read`/`bank_write`), explicit memory modes for sub-16 KB programs. * **Mouse**: full Sprinter driver wrapper (14 functions including custom cursor bitmaps). * **Environment**: `getenv`/`putenv`/`sysenv` over ESTEX `$46`. * **Time**: `getdatetime`/`setdatetime` + POSIX `time`/`localtime`/`mktime`/`asctime`/`ctime`. * **Misc**: `errno`/`strerror`/`perror`, `atexit`, `setjmp`/`longjmp`, `sleep`, full argv parsing in crt0. ## Quick start ```sh git clone sprinter-c cd sprinter-c make sdcc # one-time: fetch SDCC 4.5 binary (~25 MB) make all # build mkexe + libsprinter.lib + 27 examples make floppy # pack everything into mame/v306/IMG/mc.img cd mame/v306 && ./run_mame.sh # boot Sprinter in MAME ``` Compile a single program: ```sh cat > hello.c < int main(void) { puts("Hello, Sprinter!"); return 0; } EOF bin/sprinter-cc -o hello.exe hello.c ``` That's it ? `hello.exe` is now a valid SprintEXE you can `RUN HELLO` from the ESTEX shell. ## Memory modes Sprinter's address space is four 16 KB windows (W0 / W1 / W2 / W3). DSS allocates pages by program size ? small programs get only one page. Pick a memory mode based on what your program needs: | Mode | Code lives in | Banking | Use when | Note | |---|---|---|---|---| | `tiny` (default) | W2 (0x8100+) | no | code+data < 14 KB | | | `small` | W1-W2 (0x4100+) | no | code+data < 30 KB | | | `big` | W2 + W1 banking | yes (W1) | tiny + extra code modules | | | `huge` | W1-W2 + W3 banking | yes (W3) | small + extra code modules | | | `manual` | user-specified | optional | special layouts | Not implemented | ```sh sprinter-cc --memory small -o big.exe bigprog.c sprinter-cc --memory huge -o app.exe main.c --bank 1=engine.c --bank 2=ai.c ``` Banked functions are declared with `__banked`: ```c void engine_tick(int dt) __banked; // lives in BANK1, automatically swapped ``` ## Examples (27 total) | Example | What it demonstrates | |---|---| | `hello` | Hello world with stdio + conio Turbo-C-style colors | | `argv` | argv parsing in crt0 | | `cat` | File I/O ? read & print TEST.TXT | | `seek` | 32-bit lseek over a 100 KB file | | `ls` | Directory listing via ffirst/fnext | | `filetest` | FILE\* streams (fopen/fread/...) | | `stattest` | `stat`/`fstat` on files and directories | | `errno` | errno / strerror / perror | | `mem_test` | Page allocator + bank\_read/bank\_write | | `malloc` | Heap stress test (200+ allocations) | | `banked` | Banked code in W3 (huge mode) | | `bankedbg` | Banked code in W1 (big mode) | | `banklocl` | Bank-local static data and BSS | | `mouse` | Mouse driver in text mode | | `gfx_mous` | Mouse with custom bitmap cursor in graphics mode | | `gfx_demo` | 320?256?256 lines / rects / accelerator | | `gfx_d16` | 640?256?16 same primitives | | `gfx_text` | Bitmap-font text on graphics screen | | `timedir` | Date/time + directory listing | | `ptime` | POSIX time API | | `openenv` | open() flags + environment vars | | `conio` | conio API smoke test | | `attrprob` | Probe Sprinter text attribute byte layout | | `strtest` | string.h test (from SDCC's z80.lib) | | `stdlib` | stdlib.h test (qsort / rand / strtol / etc.) | | `assrtest` | assert() | | `rt_test` | Runtime helpers (sleep, setjmp, atexit) | ## Headers Standard: * `` ? puts / printf / FILE\* + Sprinter-specific dec/hex helpers * `` ? atoi / atof / malloc / qsort / ... (from SDCC z80.lib) * `` / `` / `` ? from SDCC z80.lib * `` ? read / write / close / lseek / unlink * `` ? open / creat + O\_RDONLY / O\_CREAT / ... * `` ? errno + error names + strerror * `` ? stat / fstat * `` / `` ? from SDCC Sprinter-specific: * `` ? putch / cputs / cprintf, textcolor / textbackground / textattr, kbhit / getch, clrscr, gotoxy, wherex/y * `` ? gfx_init/done, palette, putpixel, hline/vline/rect/fill_rect/line, text ? for both 320 and 640 modes (gfx_\*16 variants) * `` ? full 14-function driver wrapper + mouse_cursor_t with bitmap support * `` ? chdir / getcwd / mkdir / rmdir / ffirst / fnext / ffblk * `` ? getdatetime / setdatetime + POSIX time / localtime / etc. * `` ? raw ports, ESTEX/BIOS function numbers, env API * `` ? exit / \_exit / atexit * `` ? mem\_alloc\_pages / mem\_free\_block / bank\_read / bank\_write * `` ? Solid-C compatibility layer (aliases + BOOL/WORD/uint types) ## Toolchain commands ```sh make all # build mkexe + lib + every example make floppy # repack mame/v306/IMG/mc.img with all .exe files make check # 17 mkexe unit-tests make clean # remove all build artefacts make sdcc # one-time: fetch SDCC 4.5 binary ``` ### sprinter-cc options ``` sprinter-cc -o foo.exe foo.c [more.c ...] [options] --memory MODE tiny | small | big | huge | manual (default: tiny) --memory-manual SPEC explicit placement (CODE=W1|W2,DATA=W1|W2|SAME,BANKED=W1|W3) --stack-size N bytes reserved for the stack (default ~1278) --crt0=TYPE default | minimal | banked | small --bank N=FILE.c compile FILE.c into bank N (repeatable, max 15) --debug enable runtime diagnostics (defines DEBUG_RT) -I PATH extra include path -L 0xADDR / -E / -S override load / entry / stack addresses -Wl FLAG pass FLAG to sdldz80 --mkexe FLAG pass FLAG to mkexe (e.g. --mkexe -p --mkexe 0 for bank padding) -v verbose ``` ## Status What works in v1.0: * Compile / link / pack to SprintEXE ? verified on all 27 examples * Four memory modes (tiny / small / big / huge) * Graphics (both modes) with accelerator * Mouse (text + graphics cursor) * File I/O, directories, environment, time * All headers listed above Deferred to v2.0 (see `docs/TODO.md`): * **Turbo-C-style BGI graphics API** ? `initgraph` / `setcolor` / `circle` / `getimage` / `putimage` / etc. on top of our `gfx_*` primitives * Remaining Solid-C compatibility gaps (Phase 2/3) ? see `docs/solid_c_compatibility.md` * Manual memory mode * Rewrite FILE\* stream API (current implementation is very primitive and doesn't use buffers) Deferred to v3.0: * **IM2 interrupt handlers** ? research complete (`docs/im2_isr_design.md`), implementation scheduled for v3 * **Audio API** (AY-3-8910 + COVOX) ? requires IM2 * **ISA-8 slot drivers** ? requires IM2 (???) ## Documentation * `docs/TODO.md` ? roadmap and open work items * `docs/solid_c_compatibility.md` ? gap analysis vs Solid-C 2004 * `docs/im2_isr_design.md` ? interrupt handler design (v2) * `docs/converted/` ? source documentation (ESTEX, BIOS, architecture) converted to plain text for `grep` * `docs/reference/`, `docs/samples/`, `docs/memory management/` ? original Russian docs and code samples from Peters Plus ## Repository layout ``` bin/sprinter-cc one-line compiler driver (bash) toolchain/mkexe/ host-side tool: .ihx -> .exe SprintEXE toolchain/check_banks.py post-link bank size enforcer runtime/ crt0 variants (default, minimal, small, banked) bank trampolines, heap, heap_top libc/include/ headers libc/io|stdio|mem|gfx/ C and asm sources for libsprinter.lib lib/ Makefile that archives libsprinter.lib via sdar examples/ 27 example programs mame/v306/ MAME binary + Sprinter ROM/HDD images + floppy script third_party/sdcc/ vendored SDCC 4.5 (fetched via `make sdcc`) third_party/solid-c/ reference: original Sprinter native C (for compat target) docs/ documentation ``` ## License This repository contains: * Original code in `bin/`, `toolchain/`, `runtime/`, `libc/`, `lib/`, `examples/` ? MIT-licensed. * `third_party/sdcc/` ? SDCC 4.5 under GPLv2 with linking exception (see `third_party/sdcc/COPYING.txt`) * `third_party/solid-c/` ? original Sprinter Solid C, used only as a reference ## Credits * **Sprinter / Peters Plus** ? Иван Мак, Дмитрий Паринов and the original team * **SDCC** ? for the underlying Z80 compiler * **MAME** ? for the Sprinter Sp2000 emulation --- For questions / patches: see CONTRIBUTING.md (TBD) or open an issue. --- # 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` - Полный `` (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:` строки) из вывода `sdcc`. Через `-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 ? количество строк/столбцов экономии. Применимо ко всем композитным примитивам. # MDView ? Просмотрщик Markdown для Sprinter **MDView** ? программа для просмотра документов в формате *Markdown* на компьютере Sprinter (процессор Z80). Документ хранится в отдельном W3 окне и не занимает основную RAM программы. ## Возможности - Документы до **128 КБ** (8 страниц EMM по 16 КБ каждая) - До **16 384** экранных строк в индексе - Автоматический перенос слов по ширине экрана (80 столбцов) - Горизонтальный сдвиг для широких строк (блоки кода, таблицы) - Статус-бар: имя файла, диапазон строк, процент прокрутки - Спиннер в строке состояния во время загрузки и индексации - Поддержка ?мягкого? склеивания строк в абзацах и цитатах ## Запуск ``` mdview [имя_файла.md] ``` Если имя файла не задано, загружается `README.MD`. ## Управление ``` Клавиша Действие ───────────── ──────────────────────────────────────── Up Down Прокрутка на одну строку вверх / вниз PgUp PgDn Прокрутка на страницу (30 строк) Home Начало документа End Конец документа Left Right Горизонтальный сдвиг (только nowrap-строки) F1 Окно справки F8 Кодировка: CP866 ? CP1251 ? KOI8-R ? UTF-8 ? ... F10 / Esc Выход из программы ``` Кодировка определяется автоматически при открытии (BOM + эвристика по первым 4 КБ); `F8` переключает её вручную, если детекция ошиблась. 8-битные кодировки (CP866/CP1251/KOI8-R) переключаются мгновенно (ремап на отрисовке). Второй набор индекс/кэша (например UTF-8) строится лениво ? при первом переключении в него (короткая пауза со спиннером), дальше мгновенно. ## Синтаксис Markdown ### Заголовки Поддерживаются уровни H1?H4. Уровни H5 и H6 отображаются как H4. # Заголовок первого уровня ## Заголовок второго уровня ### Заголовок третьего уровня #### Заголовок четвёртого уровня ### Текстовое форматирование **Жирный текст** выделяется двойными звёздочками: `**текст**` *Курсив* выделяется одиночными звёздочками `*текст*` или знаком подчёркивания `_текст_` `Встроенный код` обозначается обратными кавычками ~~Зачёркнутый текст~~ ? двойные тильды: `~~текст~~` ### Ненумерованный список Маркеры `-`, `*` или `+`: - Первый пункт списка - Второй пункт списка - Третий пункт с достаточно длинным текстом, который при необходимости будет перенесён на следующую строку с сохранением отступа ### Нумерованный список 1. Первый элемент 2. Второй элемент 3. Третий элемент ### Цитата > Блок цитаты начинается с символа `>`. Несколько последовательных > строк одной цитаты склеиваются в единый абзац с автоматическим > переносом слов. ### Блок кода (verbatim) Блок кода заключается в тройные обратные кавычки. Внутри блока текст отображается ?как есть? без разбора Markdown: ``` #include #include int main(void) { puts("Hello, Sprinter!"); return 0; } ``` ### Горизонтальная линия Три или более символов `---`, `***` или `___` на отдельной строке: --- ## Технические характеристики - **Платформа:** Sprinter, процессор Z80 @ 21 МГц - **Кодировки:** CP866 / CP1251 / KOI8-R / UTF-8 (автоопределение, `F8`) - **Максимальный размер файла:** 128 КБ - **Максимальное число строк в индексе:** 16 384 - **Режим памяти:** small - Код программы, cтек, данные, куча ? окнa W1-W2 (32 КБ, адреса 0x4000?0xBFFF). - Буфер файла ? страницы EMM, отображаемые в W3 (0xC000?0xFFFF) ## TODO 1. **Увеличение размера документов.** Снять лимит 128 КБ: Достаточно разрешить работать с большим кол-вом страниц памяти, пока оттестированно на работе с 8-мю страницами по 16Кб. Сделано: форматированные таблицы с рамкой; поддержка кодировок CP866 / CP1251 / KOI8-R / UTF-8 с автоопределением и переключением по `F8` (второй набор строится лениво, по первому переключению). 4. **Ускорение рендеринга.** Кэш строк экрана. Оптимизация цикла вывода символов через BIOS WRCHAR (пакетный вывод, DMA). --- *MDView v0.2 · (c) 2026 Петров А.Г.* --- # Sprinter C Compiler ? v1.0 C toolchain for **Sprinter** ? the Z80-based home computer by Peters Plus, running ESTEX DSS. Host: macOS / Linux. Target: `.EXE` files in SprintEXE format. Built on top of **SDCC 4.5** (vendored in `third_party/sdcc/`). This repository adds everything Sprinter-specific: crt0, linker integration, libc wrappers over ESTEX, banked-call trampolines, graphics & accelerator API, mouse driver wrappers, and the `mkexe` utility for producing SprintEXE images. ## What you get * **`bin/sprinter-cc`** ? one-line driver: `sprinter-cc -o foo.exe foo.c` * **Memory modes**: `tiny`, `small`, `big`, `huge`, `manual` ? see below. * **stdio + conio**: printf, puts, putchar, getchar, fopen/fread/..., cprintf, cputs, putch, textcolor/textbackground/textattr, gotoxy, kbhit/getch. * **Graphics**: 320?256?256 and 640?256?16 modes, accelerator-backed primitives (hline / vline / rect / fill_rect / line via Bresenham, plus clear), bitmap-font text in both modes via BIOS character generator. * **File I/O**: POSIX (`open`/`read`/`write`/`close`/`lseek`/`unlink`/`creat`), FILE\* streams (`fopen`/`fgets`/`fwrite`/...), directory listing (`ffirst`/`fnext`), `chdir`/`getcwd`/`mkdir`/`rmdir`, `stat`/`fstat`. * **Memory**: 32 KB heap (W2-resident), banking-aware page allocator (`mem_alloc_pages`/`bank_read`/`bank_write`), explicit memory modes for sub-16 KB programs. * **Mouse**: full Sprinter driver wrapper (14 functions including custom cursor bitmaps). * **Environment**: `getenv`/`putenv`/`sysenv` over ESTEX `$46`. * **Time**: `getdatetime`/`setdatetime` + POSIX `time`/`localtime`/`mktime`/`asctime`/`ctime`. * **Misc**: `errno`/`strerror`/`perror`, `atexit`, `setjmp`/`longjmp`, `sleep`, full argv parsing in crt0. ## Quick start ```sh git clone sprinter-c cd sprinter-c make sdcc # one-time: fetch SDCC 4.5 binary (~25 MB) make all # build mkexe + libsprinter.lib + 27 examples make floppy # pack everything into mame/v306/IMG/mc.img cd mame/v306 && ./run_mame.sh # boot Sprinter in MAME ``` Compile a single program: ```sh cat > hello.c < int main(void) { puts("Hello, Sprinter!"); return 0; } EOF bin/sprinter-cc -o hello.exe hello.c ``` That's it ? `hello.exe` is now a valid SprintEXE you can `RUN HELLO` from the ESTEX shell. ## Memory modes Sprinter's address space is four 16 KB windows (W0 / W1 / W2 / W3). DSS allocates pages by program size ? small programs get only one page. Pick a memory mode based on what your program needs: | Mode | Code lives in | Banking | Use when | Note | |---|---|---|---|---| | `tiny` (default) | W2 (0x8100+) | no | code+data < 14 KB | | | `small` | W1-W2 (0x4100+) | no | code+data < 30 KB | | | `big` | W2 + W1 banking | yes (W1) | tiny + extra code modules | | | `huge` | W1-W2 + W3 banking | yes (W3) | small + extra code modules | | | `manual` | user-specified | optional | special layouts | Not implemented | ```sh sprinter-cc --memory small -o big.exe bigprog.c sprinter-cc --memory huge -o app.exe main.c --bank 1=engine.c --bank 2=ai.c ``` Banked functions are declared with `__banked`: ```c void engine_tick(int dt) __banked; // lives in BANK1, automatically swapped ``` ## Examples (27 total) | Example | What it demonstrates | |---|---| | `hello` | Hello world with stdio + conio Turbo-C-style colors | | `argv` | argv parsing in crt0 | | `cat` | File I/O ? read & print TEST.TXT | | `seek` | 32-bit lseek over a 100 KB file | | `ls` | Directory listing via ffirst/fnext | | `filetest` | FILE\* streams (fopen/fread/...) | | `stattest` | `stat`/`fstat` on files and directories | | `errno` | errno / strerror / perror | | `mem_test` | Page allocator + bank\_read/bank\_write | | `malloc` | Heap stress test (200+ allocations) | | `banked` | Banked code in W3 (huge mode) | | `bankedbg` | Banked code in W1 (big mode) | | `banklocl` | Bank-local static data and BSS | | `mouse` | Mouse driver in text mode | | `gfx_mous` | Mouse with custom bitmap cursor in graphics mode | | `gfx_demo` | 320?256?256 lines / rects / accelerator | | `gfx_d16` | 640?256?16 same primitives | | `gfx_text` | Bitmap-font text on graphics screen | | `timedir` | Date/time + directory listing | | `ptime` | POSIX time API | | `openenv` | open() flags + environment vars | | `conio` | conio API smoke test | | `attrprob` | Probe Sprinter text attribute byte layout | | `strtest` | string.h test (from SDCC's z80.lib) | | `stdlib` | stdlib.h test (qsort / rand / strtol / etc.) | | `assrtest` | assert() | | `rt_test` | Runtime helpers (sleep, setjmp, atexit) | ## Headers Standard: * `` ? puts / printf / FILE\* + Sprinter-specific dec/hex helpers * `` ? atoi / atof / malloc / qsort / ... (from SDCC z80.lib) * `` / `` / `` ? from SDCC z80.lib * `` ? read / write / close / lseek / unlink * `` ? open / creat + O\_RDONLY / O\_CREAT / ... * `` ? errno + error names + strerror * `` ? stat / fstat * `` / `` ? from SDCC Sprinter-specific: * `` ? putch / cputs / cprintf, textcolor / textbackground / textattr, kbhit / getch, clrscr, gotoxy, wherex/y * `` ? gfx_init/done, palette, putpixel, hline/vline/rect/fill_rect/line, text ? for both 320 and 640 modes (gfx_\*16 variants) * `` ? full 14-function driver wrapper + mouse_cursor_t with bitmap support * `` ? chdir / getcwd / mkdir / rmdir / ffirst / fnext / ffblk * `` ? getdatetime / setdatetime + POSIX time / localtime / etc. * `` ? raw ports, ESTEX/BIOS function numbers, env API * `` ? exit / \_exit / atexit * `` ? mem\_alloc\_pages / mem\_free\_block / bank\_read / bank\_write * `` ? Solid-C compatibility layer (aliases + BOOL/WORD/uint types) ## Toolchain commands ```sh make all # build mkexe + lib + every example make floppy # repack mame/v306/IMG/mc.img with all .exe files make check # 17 mkexe unit-tests make clean # remove all build artefacts make sdcc # one-time: fetch SDCC 4.5 binary ``` ### sprinter-cc options ``` sprinter-cc -o foo.exe foo.c [more.c ...] [options] --memory MODE tiny | small | big | huge | manual (default: tiny) --memory-manual SPEC explicit placement (CODE=W1|W2,DATA=W1|W2|SAME,BANKED=W1|W3) --stack-size N bytes reserved for the stack (default ~1278) --crt0=TYPE default | minimal | banked | small --bank N=FILE.c compile FILE.c into bank N (repeatable, max 15) --debug enable runtime diagnostics (defines DEBUG_RT) -I PATH extra include path -L 0xADDR / -E / -S override load / entry / stack addresses -Wl FLAG pass FLAG to sdldz80 --mkexe FLAG pass FLAG to mkexe (e.g. --mkexe -p --mkexe 0 for bank padding) -v verbose ``` ## Status What works in v1.0: * Compile / link / pack to SprintEXE ? verified on all 27 examples * Four memory modes (tiny / small / big / huge) * Graphics (both modes) with accelerator * Mouse (text + graphics cursor) * File I/O, directories, environment, time * All headers listed above Deferred to v2.0 (see `docs/TODO.md`): * **Turbo-C-style BGI graphics API** ? `initgraph` / `setcolor` / `circle` / `getimage` / `putimage` / etc. on top of our `gfx_*` primitives * Remaining Solid-C compatibility gaps (Phase 2/3) ? see `docs/solid_c_compatibility.md` * Manual memory mode * Rewrite FILE\* stream API (current implementation is very primitive and doesn't use buffers) Deferred to v3.0: * **IM2 interrupt handlers** ? research complete (`docs/im2_isr_design.md`), implementation scheduled for v3 * **Audio API** (AY-3-8910 + COVOX) ? requires IM2 * **ISA-8 slot drivers** ? requires IM2 (???) ## Documentation * `docs/TODO.md` ? roadmap and open work items * `docs/solid_c_compatibility.md` ? gap analysis vs Solid-C 2004 * `docs/im2_isr_design.md` ? interrupt handler design (v2) * `docs/converted/` ? source documentation (ESTEX, BIOS, architecture) converted to plain text for `grep` * `docs/reference/`, `docs/samples/`, `docs/memory management/` ? original Russian docs and code samples from Peters Plus ## Repository layout ``` bin/sprinter-cc one-line compiler driver (bash) toolchain/mkexe/ host-side tool: .ihx -> .exe SprintEXE toolchain/check_banks.py post-link bank size enforcer runtime/ crt0 variants (default, minimal, small, banked) bank trampolines, heap, heap_top libc/include/ headers libc/io|stdio|mem|gfx/ C and asm sources for libsprinter.lib lib/ Makefile that archives libsprinter.lib via sdar examples/ 27 example programs mame/v306/ MAME binary + Sprinter ROM/HDD images + floppy script third_party/sdcc/ vendored SDCC 4.5 (fetched via `make sdcc`) third_party/solid-c/ reference: original Sprinter native C (for compat target) docs/ documentation ``` ## License This repository contains: * Original code in `bin/`, `toolchain/`, `runtime/`, `libc/`, `lib/`, `examples/` ? MIT-licensed. * `third_party/sdcc/` ? SDCC 4.5 under GPLv2 with linking exception (see `third_party/sdcc/COPYING.txt`) * `third_party/solid-c/` ? original Sprinter Solid C, used only as a reference ## Credits * **Sprinter / Peters Plus** ? Иван Мак, Дмитрий Паринов and the original team * **SDCC** ? for the underlying Z80 compiler * **MAME** ? for the Sprinter Sp2000 emulation --- For questions / patches: see CONTRIBUTING.md (TBD) or open an issue. --- # 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` - Полный `` (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:` строки) из вывода `sdcc`. Через `-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 ? количество строк/столбцов экономии. Применимо ко всем композитным примитивам. # MDView ? Просмотрщик Markdown для Sprinter **MDView** ? программа для просмотра документов в формате *Markdown* на компьютере Sprinter (процессор Z80). Документ хранится в отдельном W3 окне и не занимает основную RAM программы. ## Возможности - Документы до **128 КБ** (8 страниц EMM по 16 КБ каждая) - До **16 384** экранных строк в индексе - Автоматический перенос слов по ширине экрана (80 столбцов) - Горизонтальный сдвиг для широких строк (блоки кода, таблицы) - Статус-бар: имя файла, диапазон строк, процент прокрутки - Спиннер в строке состояния во время загрузки и индексации - Поддержка ?мягкого? склеивания строк в абзацах и цитатах ## Запуск ``` mdview [имя_файла.md] ``` Если имя файла не задано, загружается `README.MD`. ## Управление ``` Клавиша Действие ───────────── ──────────────────────────────────────── Up Down Прокрутка на одну строку вверх / вниз PgUp PgDn Прокрутка на страницу (30 строк) Home Начало документа End Конец документа Left Right Горизонтальный сдвиг (только nowrap-строки) F1 Окно справки F8 Кодировка: CP866 ? CP1251 ? KOI8-R ? UTF-8 ? ... F10 / Esc Выход из программы ``` Кодировка определяется автоматически при открытии (BOM + эвристика по первым 4 КБ); `F8` переключает её вручную, если детекция ошиблась. 8-битные кодировки (CP866/CP1251/KOI8-R) переключаются мгновенно (ремап на отрисовке). Второй набор индекс/кэша (например UTF-8) строится лениво ? при первом переключении в него (короткая пауза со спиннером), дальше мгновенно. ## Синтаксис Markdown ### Заголовки Поддерживаются уровни H1?H4. Уровни H5 и H6 отображаются как H4. # Заголовок первого уровня ## Заголовок второго уровня ### Заголовок третьего уровня #### Заголовок четвёртого уровня ### Текстовое форматирование **Жирный текст** выделяется двойными звёздочками: `**текст**` *Курсив* выделяется одиночными звёздочками `*текст*` или знаком подчёркивания `_текст_` `Встроенный код` обозначается обратными кавычками ~~Зачёркнутый текст~~ ? двойные тильды: `~~текст~~` ### Ненумерованный список Маркеры `-`, `*` или `+`: - Первый пункт списка - Второй пункт списка - Третий пункт с достаточно длинным текстом, который при необходимости будет перенесён на следующую строку с сохранением отступа ### Нумерованный список 1. Первый элемент 2. Второй элемент 3. Третий элемент ### Цитата > Блок цитаты начинается с символа `>`. Несколько последовательных > строк одной цитаты склеиваются в единый абзац с автоматическим > переносом слов. ### Блок кода (verbatim) Блок кода заключается в тройные обратные кавычки. Внутри блока текст отображается ?как есть? без разбора Markdown: ``` #include #include int main(void) { puts("Hello, Sprinter!"); return 0; } ``` ### Горизонтальная линия Три или более символов `---`, `***` или `___` на отдельной строке: --- ## Технические характеристики - **Платформа:** Sprinter, процессор Z80 @ 21 МГц - **Кодировки:** CP866 / CP1251 / KOI8-R / UTF-8 (автоопределение, `F8`) - **Максимальный размер файла:** 128 КБ - **Максимальное число строк в индексе:** 16 384 - **Максимальная длина строки в рендер-кэше:** 255 ячеек (см. ограничение ниже) - **Режим памяти:** small - Код программы, cтек, данные, куча ? окнa W1-W2 (32 КБ, адреса 0x4000?0xBFFF). - Буфер файла ? страницы EMM, отображаемые в W3 (0xC000?0xFFFF) ## Известные ограничения ### Длина строки в MD-режиме ? 255 ячеек Рендер-кэш хранит каждую логическую строку как **не более 255 пар (символ, атрибут)** ? константа `MAX_CACHE_LINE_LEN`. Лимит задан типами: `g_ncells` и `cache_rec_t.len` ? `uint8_t`. Касается всех строк, но заметнее всего на **горизонтально скроллируемых** строках (блоки кода и строки таблиц, флаг `IF_HSCROLL`), которые в MD-режиме можно листать вправо. **Что происходит с более длинной строкой:** при индексации `gc_put()` молча отбрасывает каждую ячейку после 255-й (`if (g_ncells < MAX_CACHE_LINE_LEN)`). В кэш попадают только первые 255 ячеек, остаток **теряется** ? до него нельзя доскроллить и **нет маркера обрезки** на 255-й позиции (маркер `>` означает лишь ?есть ещё в пределах кэша?). Переполнения буфера нет ? `gc_put` проверяет границу. Важно: ?255 ячеек? ? это **отрендеренная ширина**, не байты исходника. Табы в коде разворачиваются в пробелы (до `TAB_STOP`), а ячейки таблицы добиваются пробелами до ширины колонки + рамки `│` ? поэтому кап достигается раньше, чем 255 ?полезных? символов. > **RAW-режим (`F2`) этого лимита не имеет** ? он рисует прямо из файла > побайтово, длинные строки видны целиком (через wrap `F3` или гориз. скролл). **Идея снятия лимита** (оценка, не реализовано) ? расширить длину до `uint16_t`: - `cache_rec_t.len` `uint8_t?uint16_t` ? структура остаётся **ровно 8 байт** (len съедает один pad-байт), адресация `idx<<3` не меняется. Бесплатно. - `g_ncells` / `g_ncells_at_space` ? `uint16_t` ? главная цена по **коду/скорости**: 16-битная арифметика на Z80 в горячем `gc_put` (вызов на каждую ячейку) и в scan-циклах. Ориентир: **+0.2?0.4 КБ кода** + замедление индексации. - Буфер `g_cells[MAX_CACHE_LINE_LEN*2]` в near-RAM (W2) ? главная цена по **памяти**: `2 ? кап` байт. Сейчас 510 Б; кап 512 ? +0.5 КБ, 1024 ? +1.5 КБ. EMM-кэш контента (1 МБ) длинные строки тянет легко ? узкое место именно near. - `viewport_x` (и копия в `docset_t`) + потолок `max_vx` (сейчас 248) ? `uint16_t`, иначе хранить >255 можно, а доскроллить нельзя. Плюс `widths[]`/`ccx` в таблицах/коде. Реалистичный компромисс ? кап 512?1023: хватит почти всем листингам/таблицам, цена ~+0.3 КБ кода и +0.5?1.5 КБ near-RAM. **Дешёвая полумера без `uint16_t`** ? ставить честный маркер обрезки на 255-й позиции, чтобы потеря была видна. ## TODO 1. **Увеличение размера документов.** Снять лимит 128 КБ: Достаточно разрешить работать с большим кол-вом страниц памяти, пока оттестированно на работе с 8-мю страницами по 16Кб. Сделано: форматированные таблицы с рамкой; поддержка кодировок CP866 / CP1251 / KOI8-R / UTF-8 с автоопределением и переключением по `F8` (второй набор строится лениво, по первому переключению). 4. **Ускорение рендеринга.** Кэш строк экрана. Оптимизация цикла вывода символов через BIOS WRCHAR (пакетный вывод, DMA). --- *MDView v0.2 · (c) 2026 Петров А.Г.* --- # Sprinter C Compiler ? v1.0 C toolchain for **Sprinter** ? the Z80-based home computer by Peters Plus, running ESTEX DSS. Host: macOS / Linux. Target: `.EXE` files in SprintEXE format. Built on top of **SDCC 4.5** (vendored in `third_party/sdcc/`). This repository adds everything Sprinter-specific: crt0, linker integration, libc wrappers over ESTEX, banked-call trampolines, graphics & accelerator API, mouse driver wrappers, and the `mkexe` utility for producing SprintEXE images. ## What you get * **`bin/sprinter-cc`** ? one-line driver: `sprinter-cc -o foo.exe foo.c` * **Memory modes**: `tiny`, `small`, `big`, `huge`, `manual` ? see below. * **stdio + conio**: printf, puts, putchar, getchar, fopen/fread/..., cprintf, cputs, putch, textcolor/textbackground/textattr, gotoxy, kbhit/getch. * **Graphics**: 320?256?256 and 640?256?16 modes, accelerator-backed primitives (hline / vline / rect / fill_rect / line via Bresenham, plus clear), bitmap-font text in both modes via BIOS character generator. * **File I/O**: POSIX (`open`/`read`/`write`/`close`/`lseek`/`unlink`/`creat`), FILE\* streams (`fopen`/`fgets`/`fwrite`/...), directory listing (`ffirst`/`fnext`), `chdir`/`getcwd`/`mkdir`/`rmdir`, `stat`/`fstat`. * **Memory**: 32 KB heap (W2-resident), banking-aware page allocator (`mem_alloc_pages`/`bank_read`/`bank_write`), explicit memory modes for sub-16 KB programs. * **Mouse**: full Sprinter driver wrapper (14 functions including custom cursor bitmaps). * **Environment**: `getenv`/`putenv`/`sysenv` over ESTEX `$46`. * **Time**: `getdatetime`/`setdatetime` + POSIX `time`/`localtime`/`mktime`/`asctime`/`ctime`. * **Misc**: `errno`/`strerror`/`perror`, `atexit`, `setjmp`/`longjmp`, `sleep`, full argv parsing in crt0. ## Quick start ```sh git clone sprinter-c cd sprinter-c make sdcc # one-time: fetch SDCC 4.5 binary (~25 MB) make all # build mkexe + libsprinter.lib + 27 examples make floppy # pack everything into mame/v306/IMG/mc.img cd mame/v306 && ./run_mame.sh # boot Sprinter in MAME ``` Compile a single program: ```sh cat > hello.c < int main(void) { puts("Hello, Sprinter!"); return 0; } EOF bin/sprinter-cc -o hello.exe hello.c ``` That's it ? `hello.exe` is now a valid SprintEXE you can `RUN HELLO` from the ESTEX shell. ## Memory modes Sprinter's address space is four 16 KB windows (W0 / W1 / W2 / W3). DSS allocates pages by program size ? small programs get only one page. Pick a memory mode based on what your program needs: | Mode | Code lives in | Banking | Use when | Note | |---|---|---|---|---| | `tiny` (default) | W2 (0x8100+) | no | code+data < 14 KB | | | `small` | W1-W2 (0x4100+) | no | code+data < 30 KB | | | `big` | W2 + W1 banking | yes (W1) | tiny + extra code modules | | | `huge` | W1-W2 + W3 banking | yes (W3) | small + extra code modules | | | `manual` | user-specified | optional | special layouts | Not implemented | ```sh sprinter-cc --memory small -o big.exe bigprog.c sprinter-cc --memory huge -o app.exe main.c --bank 1=engine.c --bank 2=ai.c ``` Banked functions are declared with `__banked`: ```c void engine_tick(int dt) __banked; // lives in BANK1, automatically swapped ``` ## Examples (27 total) | Example | What it demonstrates | |---|---| | `hello` | Hello world with stdio + conio Turbo-C-style colors | | `argv` | argv parsing in crt0 | | `cat` | File I/O ? read & print TEST.TXT | | `seek` | 32-bit lseek over a 100 KB file | | `ls` | Directory listing via ffirst/fnext | | `filetest` | FILE\* streams (fopen/fread/...) | | `stattest` | `stat`/`fstat` on files and directories | | `errno` | errno / strerror / perror | | `mem_test` | Page allocator + bank\_read/bank\_write | | `malloc` | Heap stress test (200+ allocations) | | `banked` | Banked code in W3 (huge mode) | | `bankedbg` | Banked code in W1 (big mode) | | `banklocl` | Bank-local static data and BSS | | `mouse` | Mouse driver in text mode | | `gfx_mous` | Mouse with custom bitmap cursor in graphics mode | | `gfx_demo` | 320?256?256 lines / rects / accelerator | | `gfx_d16` | 640?256?16 same primitives | | `gfx_text` | Bitmap-font text on graphics screen | | `timedir` | Date/time + directory listing | | `ptime` | POSIX time API | | `openenv` | open() flags + environment vars | | `conio` | conio API smoke test | | `attrprob` | Probe Sprinter text attribute byte layout | | `strtest` | string.h test (from SDCC's z80.lib) | | `stdlib` | stdlib.h test (qsort / rand / strtol / etc.) | | `assrtest` | assert() | | `rt_test` | Runtime helpers (sleep, setjmp, atexit) | ## Headers Standard: * `` ? puts / printf / FILE\* + Sprinter-specific dec/hex helpers * `` ? atoi / atof / malloc / qsort / ... (from SDCC z80.lib) * `` / `` / `` ? from SDCC z80.lib * `` ? read / write / close / lseek / unlink * `` ? open / creat + O\_RDONLY / O\_CREAT / ... * `` ? errno + error names + strerror * `` ? stat / fstat * `` / `` ? from SDCC Sprinter-specific: * `` ? putch / cputs / cprintf, textcolor / textbackground / textattr, kbhit / getch, clrscr, gotoxy, wherex/y * `` ? gfx_init/done, palette, putpixel, hline/vline/rect/fill_rect/line, text ? for both 320 and 640 modes (gfx_\*16 variants) * `` ? full 14-function driver wrapper + mouse_cursor_t with bitmap support * `` ? chdir / getcwd / mkdir / rmdir / ffirst / fnext / ffblk * `` ? getdatetime / setdatetime + POSIX time / localtime / etc. * `` ? raw ports, ESTEX/BIOS function numbers, env API * `` ? exit / \_exit / atexit * `` ? mem\_alloc\_pages / mem\_free\_block / bank\_read / bank\_write * `` ? Solid-C compatibility layer (aliases + BOOL/WORD/uint types) ## Toolchain commands ```sh make all # build mkexe + lib + every example make floppy # repack mame/v306/IMG/mc.img with all .exe files make check # 17 mkexe unit-tests make clean # remove all build artefacts make sdcc # one-time: fetch SDCC 4.5 binary ``` ### sprinter-cc options ``` sprinter-cc -o foo.exe foo.c [more.c ...] [options] --memory MODE tiny | small | big | huge | manual (default: tiny) --memory-manual SPEC explicit placement (CODE=W1|W2,DATA=W1|W2|SAME,BANKED=W1|W3) --stack-size N bytes reserved for the stack (default ~1278) --crt0=TYPE default | minimal | banked | small --bank N=FILE.c compile FILE.c into bank N (repeatable, max 15) --debug enable runtime diagnostics (defines DEBUG_RT) -I PATH extra include path -L 0xADDR / -E / -S override load / entry / stack addresses -Wl FLAG pass FLAG to sdldz80 --mkexe FLAG pass FLAG to mkexe (e.g. --mkexe -p --mkexe 0 for bank padding) -v verbose ``` ## Status What works in v1.0: * Compile / link / pack to SprintEXE ? verified on all 27 examples * Four memory modes (tiny / small / big / huge) * Graphics (both modes) with accelerator * Mouse (text + graphics cursor) * File I/O, directories, environment, time * All headers listed above Deferred to v2.0 (see `docs/TODO.md`): * **Turbo-C-style BGI graphics API** ? `initgraph` / `setcolor` / `circle` / `getimage` / `putimage` / etc. on top of our `gfx_*` primitives * Remaining Solid-C compatibility gaps (Phase 2/3) ? see `docs/solid_c_compatibility.md` * Manual memory mode * Rewrite FILE\* stream API (current implementation is very primitive and doesn't use buffers) Deferred to v3.0: * **IM2 interrupt handlers** ? research complete (`docs/im2_isr_design.md`), implementation scheduled for v3 * **Audio API** (AY-3-8910 + COVOX) ? requires IM2 * **ISA-8 slot drivers** ? requires IM2 (???) ## Documentation * `docs/TODO.md` ? roadmap and open work items * `docs/solid_c_compatibility.md` ? gap analysis vs Solid-C 2004 * `docs/im2_isr_design.md` ? interrupt handler design (v2) * `docs/converted/` ? source documentation (ESTEX, BIOS, architecture) converted to plain text for `grep` * `docs/reference/`, `docs/samples/`, `docs/memory management/` ? original Russian docs and code samples from Peters Plus ## Repository layout ``` bin/sprinter-cc one-line compiler driver (bash) toolchain/mkexe/ host-side tool: .ihx -> .exe SprintEXE toolchain/check_banks.py post-link bank size enforcer runtime/ crt0 variants (default, minimal, small, banked) bank trampolines, heap, heap_top libc/include/ headers libc/io|stdio|mem|gfx/ C and asm sources for libsprinter.lib lib/ Makefile that archives libsprinter.lib via sdar examples/ 27 example programs mame/v306/ MAME binary + Sprinter ROM/HDD images + floppy script third_party/sdcc/ vendored SDCC 4.5 (fetched via `make sdcc`) third_party/solid-c/ reference: original Sprinter native C (for compat target) docs/ documentation ``` ## License This repository contains: * Original code in `bin/`, `toolchain/`, `runtime/`, `libc/`, `lib/`, `examples/` ? MIT-licensed. * `third_party/sdcc/` ? SDCC 4.5 under GPLv2 with linking exception (see `third_party/sdcc/COPYING.txt`) * `third_party/solid-c/` ? original Sprinter Solid C, used only as a reference ## Credits * **Sprinter / Peters Plus** ? Иван Мак, Дмитрий Паринов and the original team * **SDCC** ? for the underlying Z80 compiler * **MAME** ? for the Sprinter Sp2000 emulation --- For questions / patches: see CONTRIBUTING.md (TBD) or open an issue. --- # 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` - Полный `` (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:` строки) из вывода `sdcc`. Через `-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 ? количество строк/столбцов экономии. Применимо ко всем композитным примитивам. # MDView ? Просмотрщик Markdown для Sprinter **MDView** ? программа для просмотра документов в формате *Markdown* на компьютере Sprinter (процессор Z80). Документ хранится в отдельном W3 окне и не занимает основную RAM программы. ## Возможности - Документы до **128 КБ** (8 страниц EMM по 16 КБ каждая) - До **16 384** экранных строк в индексе - Автоматический перенос слов по ширине экрана (80 столбцов) - Горизонтальный сдвиг для широких строк (блоки кода, таблицы) - Статус-бар: имя файла, диапазон строк, процент прокрутки - Спиннер в строке состояния во время загрузки и индексации - Поддержка ?мягкого? склеивания строк в абзацах и цитатах ## Запуск ``` mdview [имя_файла.md] ``` Если имя файла не задано, загружается `README.MD`. ## Управление ``` Клавиша Действие ───────────── ──────────────────────────────────────── Up Down Прокрутка на одну строку вверх / вниз PgUp PgDn Прокрутка на страницу (30 строк) Home Начало документа End Конец документа Left Right Горизонтальный сдвиг (только nowrap-строки) F1 Окно справки F8 Кодировка: CP866 ? CP1251 ? KOI8-R ? UTF-8 ? ... F10 / Esc Выход из программы ``` Кодировка определяется автоматически при открытии (BOM + эвристика по первым 4 КБ); `F8` переключает её вручную, если детекция ошиблась. 8-битные кодировки (CP866/CP1251/KOI8-R) переключаются мгновенно (ремап на отрисовке). Второй набор индекс/кэша (например UTF-8) строится лениво ? при первом переключении в него (короткая пауза со спиннером), дальше мгновенно. ## Синтаксис Markdown ### Заголовки Поддерживаются уровни H1?H4. Уровни H5 и H6 отображаются как H4. # Заголовок первого уровня ## Заголовок второго уровня ### Заголовок третьего уровня #### Заголовок четвёртого уровня ### Текстовое форматирование **Жирный текст** выделяется двойными звёздочками: `**текст**` *Курсив* выделяется одиночными звёздочками `*текст*` или знаком подчёркивания `_текст_` `Встроенный код` обозначается обратными кавычками ~~Зачёркнутый текст~~ ? двойные тильды: `~~текст~~` ### Ненумерованный список Маркеры `-`, `*` или `+`: - Первый пункт списка - Второй пункт списка - Третий пункт с достаточно длинным текстом, который при необходимости будет перенесён на следующую строку с сохранением отступа ### Нумерованный список 1. Первый элемент 2. Второй элемент 3. Третий элемент ### Цитата > Блок цитаты начинается с символа `>`. Несколько последовательных > строк одной цитаты склеиваются в единый абзац с автоматическим > переносом слов. ### Блок кода (verbatim) Блок кода заключается в тройные обратные кавычки. Внутри блока текст отображается ?как есть? без разбора Markdown: ``` #include #include int main(void) { puts("Hello, Sprinter!"); return 0; } ``` ### Горизонтальная линия Три или более символов `---`, `***` или `___` на отдельной строке: --- ## Технические характеристики - **Платформа:** Sprinter, процессор Z80 @ 21 МГц - **Кодировки:** CP866 / CP1251 / KOI8-R / UTF-8 (автоопределение, `F8`) - **Максимальный размер файла:** 128 КБ - **Максимальное число строк в индексе:** 16 384 - **Режим памяти:** small - Код программы, cтек, данные, куча ? окнa W1-W2 (32 КБ, адреса 0x4000?0xBFFF). - Буфер файла ? страницы EMM, отображаемые в W3 (0xC000?0xFFFF) ## TODO 1. **Увеличение размера документов.** Снять лимит 128 КБ: Достаточно разрешить работать с большим кол-вом страниц памяти, пока оттестированно на работе с 8-мю страницами по 16Кб. Сделано: форматированные таблицы с рамкой; поддержка кодировок CP866 / CP1251 / KOI8-R / UTF-8 с автоопределением и переключением по `F8` (второй набор строится лениво, по первому переключению). 4. **Ускорение рендеринга.** Кэш строк экрана. Оптимизация цикла вывода символов через BIOS WRCHAR (пакетный вывод, DMA). --- *MDView v0.2 · (c) 2026 Петров А.Г.* --- # Sprinter C Compiler ? v1.0 C toolchain for **Sprinter** ? the Z80-based home computer by Peters Plus, running ESTEX DSS. Host: macOS / Linux. Target: `.EXE` files in SprintEXE format. Built on top of **SDCC 4.5** (vendored in `third_party/sdcc/`). This repository adds everything Sprinter-specific: crt0, linker integration, libc wrappers over ESTEX, banked-call trampolines, graphics & accelerator API, mouse driver wrappers, and the `mkexe` utility for producing SprintEXE images. ## What you get * **`bin/sprinter-cc`** ? one-line driver: `sprinter-cc -o foo.exe foo.c` * **Memory modes**: `tiny`, `small`, `big`, `huge`, `manual` ? see below. * **stdio + conio**: printf, puts, putchar, getchar, fopen/fread/..., cprintf, cputs, putch, textcolor/textbackground/textattr, gotoxy, kbhit/getch. * **Graphics**: 320?256?256 and 640?256?16 modes, accelerator-backed primitives (hline / vline / rect / fill_rect / line via Bresenham, plus clear), bitmap-font text in both modes via BIOS character generator. * **File I/O**: POSIX (`open`/`read`/`write`/`close`/`lseek`/`unlink`/`creat`), FILE\* streams (`fopen`/`fgets`/`fwrite`/...), directory listing (`ffirst`/`fnext`), `chdir`/`getcwd`/`mkdir`/`rmdir`, `stat`/`fstat`. * **Memory**: 32 KB heap (W2-resident), banking-aware page allocator (`mem_alloc_pages`/`bank_read`/`bank_write`), explicit memory modes for sub-16 KB programs. * **Mouse**: full Sprinter driver wrapper (14 functions including custom cursor bitmaps). * **Environment**: `getenv`/`putenv`/`sysenv` over ESTEX `$46`. * **Time**: `getdatetime`/`setdatetime` + POSIX `time`/`localtime`/`mktime`/`asctime`/`ctime`. * **Misc**: `errno`/`strerror`/`perror`, `atexit`, `setjmp`/`longjmp`, `sleep`, full argv parsing in crt0. ## Quick start ```sh git clone sprinter-c cd sprinter-c make sdcc # one-time: fetch SDCC 4.5 binary (~25 MB) make all # build mkexe + libsprinter.lib + 27 examples make floppy # pack everything into mame/v306/IMG/mc.img cd mame/v306 && ./run_mame.sh # boot Sprinter in MAME ``` Compile a single program: ```sh cat > hello.c < int main(void) { puts("Hello, Sprinter!"); return 0; } EOF bin/sprinter-cc -o hello.exe hello.c ``` That's it ? `hello.exe` is now a valid SprintEXE you can `RUN HELLO` from the ESTEX shell. ## Memory modes Sprinter's address space is four 16 KB windows (W0 / W1 / W2 / W3). DSS allocates pages by program size ? small programs get only one page. Pick a memory mode based on what your program needs: | Mode | Code lives in | Banking | Use when | Note | |---|---|---|---|---| | `tiny` (default) | W2 (0x8100+) | no | code+data < 14 KB | | | `small` | W1-W2 (0x4100+) | no | code+data < 30 KB | | | `big` | W2 + W1 banking | yes (W1) | tiny + extra code modules | | | `huge` | W1-W2 + W3 banking | yes (W3) | small + extra code modules | | | `manual` | user-specified | optional | special layouts | Not implemented | ```sh sprinter-cc --memory small -o big.exe bigprog.c sprinter-cc --memory huge -o app.exe main.c --bank 1=engine.c --bank 2=ai.c ``` Banked functions are declared with `__banked`: ```c void engine_tick(int dt) __banked; // lives in BANK1, automatically swapped ``` ## Examples (27 total) | Example | What it demonstrates | |---|---| | `hello` | Hello world with stdio + conio Turbo-C-style colors | | `argv` | argv parsing in crt0 | | `cat` | File I/O ? read & print TEST.TXT | | `seek` | 32-bit lseek over a 100 KB file | | `ls` | Directory listing via ffirst/fnext | | `filetest` | FILE\* streams (fopen/fread/...) | | `stattest` | `stat`/`fstat` on files and directories | | `errno` | errno / strerror / perror | | `mem_test` | Page allocator + bank\_read/bank\_write | | `malloc` | Heap stress test (200+ allocations) | | `banked` | Banked code in W3 (huge mode) | | `bankedbg` | Banked code in W1 (big mode) | | `banklocl` | Bank-local static data and BSS | | `mouse` | Mouse driver in text mode | | `gfx_mous` | Mouse with custom bitmap cursor in graphics mode | | `gfx_demo` | 320?256?256 lines / rects / accelerator | | `gfx_d16` | 640?256?16 same primitives | | `gfx_text` | Bitmap-font text on graphics screen | | `timedir` | Date/time + directory listing | | `ptime` | POSIX time API | | `openenv` | open() flags + environment vars | | `conio` | conio API smoke test | | `attrprob` | Probe Sprinter text attribute byte layout | | `strtest` | string.h test (from SDCC's z80.lib) | | `stdlib` | stdlib.h test (qsort / rand / strtol / etc.) | | `assrtest` | assert() | | `rt_test` | Runtime helpers (sleep, setjmp, atexit) | ## Headers Standard: * `` ? puts / printf / FILE\* + Sprinter-specific dec/hex helpers * `` ? atoi / atof / malloc / qsort / ... (from SDCC z80.lib) * `` / `` / `` ? from SDCC z80.lib * `` ? read / write / close / lseek / unlink * `` ? open / creat + O\_RDONLY / O\_CREAT / ... * `` ? errno + error names + strerror * `` ? stat / fstat * `` / `` ? from SDCC Sprinter-specific: * `` ? putch / cputs / cprintf, textcolor / textbackground / textattr, kbhit / getch, clrscr, gotoxy, wherex/y * `` ? gfx_init/done, palette, putpixel, hline/vline/rect/fill_rect/line, text ? for both 320 and 640 modes (gfx_\*16 variants) * `` ? full 14-function driver wrapper + mouse_cursor_t with bitmap support * `` ? chdir / getcwd / mkdir / rmdir / ffirst / fnext / ffblk * `` ? getdatetime / setdatetime + POSIX time / localtime / etc. * `` ? raw ports, ESTEX/BIOS function numbers, env API * `` ? exit / \_exit / atexit * `` ? mem\_alloc\_pages / mem\_free\_block / bank\_read / bank\_write * `` ? Solid-C compatibility layer (aliases + BOOL/WORD/uint types) ## Toolchain commands ```sh make all # build mkexe + lib + every example make floppy # repack mame/v306/IMG/mc.img with all .exe files make check # 17 mkexe unit-tests make clean # remove all build artefacts make sdcc # one-time: fetch SDCC 4.5 binary ``` ### sprinter-cc options ``` sprinter-cc -o foo.exe foo.c [more.c ...] [options] --memory MODE tiny | small | big | huge | manual (default: tiny) --memory-manual SPEC explicit placement (CODE=W1|W2,DATA=W1|W2|SAME,BANKED=W1|W3) --stack-size N bytes reserved for the stack (default ~1278) --crt0=TYPE default | minimal | banked | small --bank N=FILE.c compile FILE.c into bank N (repeatable, max 15) --debug enable runtime diagnostics (defines DEBUG_RT) -I PATH extra include path -L 0xADDR / -E / -S override load / entry / stack addresses -Wl FLAG pass FLAG to sdldz80 --mkexe FLAG pass FLAG to mkexe (e.g. --mkexe -p --mkexe 0 for bank padding) -v verbose ``` ## Status What works in v1.0: * Compile / link / pack to SprintEXE ? verified on all 27 examples * Four memory modes (tiny / small / big / huge) * Graphics (both modes) with accelerator * Mouse (text + graphics cursor) * File I/O, directories, environment, time * All headers listed above Deferred to v2.0 (see `docs/TODO.md`): * **Turbo-C-style BGI graphics API** ? `initgraph` / `setcolor` / `circle` / `getimage` / `putimage` / etc. on top of our `gfx_*` primitives * Remaining Solid-C compatibility gaps (Phase 2/3) ? see `docs/solid_c_compatibility.md` * Manual memory mode * Rewrite FILE\* stream API (current implementation is very primitive and doesn't use buffers) Deferred to v3.0: * **IM2 interrupt handlers** ? research complete (`docs/im2_isr_design.md`), implementation scheduled for v3 * **Audio API** (AY-3-8910 + COVOX) ? requires IM2 * **ISA-8 slot drivers** ? requires IM2 (???) ## Documentation * `docs/TODO.md` ? roadmap and open work items * `docs/solid_c_compatibility.md` ? gap analysis vs Solid-C 2004 * `docs/im2_isr_design.md` ? interrupt handler design (v2) * `docs/converted/` ? source documentation (ESTEX, BIOS, architecture) converted to plain text for `grep` * `docs/reference/`, `docs/samples/`, `docs/memory management/` ? original Russian docs and code samples from Peters Plus ## Repository layout ``` bin/sprinter-cc one-line compiler driver (bash) toolchain/mkexe/ host-side tool: .ihx -> .exe SprintEXE toolchain/check_banks.py post-link bank size enforcer runtime/ crt0 variants (default, minimal, small, banked) bank trampolines, heap, heap_top libc/include/ headers libc/io|stdio|mem|gfx/ C and asm sources for libsprinter.lib lib/ Makefile that archives libsprinter.lib via sdar examples/ 27 example programs mame/v306/ MAME binary + Sprinter ROM/HDD images + floppy script third_party/sdcc/ vendored SDCC 4.5 (fetched via `make sdcc`) third_party/solid-c/ reference: original Sprinter native C (for compat target) docs/ documentation ``` ## License This repository contains: * Original code in `bin/`, `toolchain/`, `runtime/`, `libc/`, `lib/`, `examples/` ? MIT-licensed. * `third_party/sdcc/` ? SDCC 4.5 under GPLv2 with linking exception (see `third_party/sdcc/COPYING.txt`) * `third_party/solid-c/` ? original Sprinter Solid C, used only as a reference ## Credits * **Sprinter / Peters Plus** ? Иван Мак, Дмитрий Паринов and the original team * **SDCC** ? for the underlying Z80 compiler * **MAME** ? for the Sprinter Sp2000 emulation --- For questions / patches: see CONTRIBUTING.md (TBD) or open an issue. --- # 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` - Полный `` (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:` строки) из вывода `sdcc`. Через `-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 ? количество строк/столбцов экономии. Применимо ко всем композитным примитивам. # MDView ? Просмотрщик Markdown для Sprinter **MDView** ? программа для просмотра документов в формате *Markdown* на компьютере Sprinter (процессор Z80). Документ хранится в отдельном W3 окне и не занимает основную RAM программы. ## Возможности - Документы до **128 КБ** (8 страниц EMM по 16 КБ каждая) - До **16 384** экранных строк в индексе - Автоматический перенос слов по ширине экрана (80 столбцов) - Горизонтальный сдвиг для широких строк (блоки кода, таблицы) - Статус-бар: имя файла, диапазон строк, процент прокрутки - Спиннер в строке состояния во время загрузки и индексации - Поддержка ?мягкого? склеивания строк в абзацах и цитатах ## Запуск ``` mdview [имя_файла.md] ``` Если имя файла не задано, загружается `README.MD`. ## Управление ``` Клавиша Действие ───────────── ──────────────────────────────────────── Up Down Прокрутка на одну строку вверх / вниз PgUp PgDn Прокрутка на страницу (30 строк) Home Начало документа End Конец документа Left Right Горизонтальный сдвиг (только nowrap-строки) F1 Окно справки F8 Кодировка: CP866 ? CP1251 ? KOI8-R ? UTF-8 ? ... F10 / Esc Выход из программы ``` Кодировка определяется автоматически при открытии (BOM + эвристика по первым 4 КБ); `F8` переключает её вручную, если детекция ошиблась. 8-битные кодировки (CP866/CP1251/KOI8-R) переключаются мгновенно (ремап на отрисовке). Второй набор индекс/кэша (например UTF-8) строится лениво ? при первом переключении в него (короткая пауза со спиннером), дальше мгновенно. ## Синтаксис Markdown ### Заголовки Поддерживаются уровни H1?H4. Уровни H5 и H6 отображаются как H4. # Заголовок первого уровня ## Заголовок второго уровня ### Заголовок третьего уровня #### Заголовок четвёртого уровня ### Текстовое форматирование **Жирный текст** выделяется двойными звёздочками: `**текст**` *Курсив* выделяется одиночными звёздочками `*текст*` или знаком подчёркивания `_текст_` `Встроенный код` обозначается обратными кавычками ~~Зачёркнутый текст~~ ? двойные тильды: `~~текст~~` ### Ненумерованный список Маркеры `-`, `*` или `+`: - Первый пункт списка - Второй пункт списка - Третий пункт с достаточно длинным текстом, который при необходимости будет перенесён на следующую строку с сохранением отступа ### Нумерованный список 1. Первый элемент 2. Второй элемент 3. Третий элемент ### Цитата > Блок цитаты начинается с символа `>`. Несколько последовательных > строк одной цитаты склеиваются в единый абзац с автоматическим > переносом слов. ### Блок кода (verbatim) Блок кода заключается в тройные обратные кавычки. Внутри блока текст отображается ?как есть? без разбора Markdown: ``` #include #include int main(void) { puts("Hello, Sprinter!"); return 0; } ``` ### Горизонтальная линия Три или более символов `---`, `***` или `___` на отдельной строке: --- ## Технические характеристики - **Платформа:** Sprinter, процессор Z80 @ 21 МГц - **Кодировки:** CP866 / CP1251 / KOI8-R / UTF-8 (автоопределение, `F8`) - **Максимальный размер файла:** 128 КБ - **Максимальное число строк в индексе:** 16 384 - **Максимальная длина строки в рендер-кэше:** 255 ячеек (см. ограничение ниже) - **Режим памяти:** small - Код программы, cтек, данные, куча ? окнa W1-W2 (32 КБ, адреса 0x4000?0xBFFF). - Буфер файла ? страницы EMM, отображаемые в W3 (0xC000?0xFFFF) ## Известные ограничения ### Длина строки в MD-режиме ? 255 ячеек Рендер-кэш хранит каждую логическую строку как **не более 255 пар (символ, атрибут)** ? константа `MAX_CACHE_LINE_LEN`. Лимит задан типами: `g_ncells` и `cache_rec_t.len` ? `uint8_t`. Касается всех строк, но заметнее всего на **горизонтально скроллируемых** строках (блоки кода и строки таблиц, флаг `IF_HSCROLL`), которые в MD-режиме можно листать вправо. **Что происходит с более длинной строкой:** при индексации `gc_put()` молча отбрасывает каждую ячейку после 255-й (`if (g_ncells < MAX_CACHE_LINE_LEN)`). В кэш попадают только первые 255 ячеек, остаток **теряется** ? до него нельзя доскроллить и **нет маркера обрезки** на 255-й позиции (маркер `>` означает лишь ?есть ещё в пределах кэша?). Переполнения буфера нет ? `gc_put` проверяет границу. Важно: ?255 ячеек? ? это **отрендеренная ширина**, не байты исходника. Табы в коде разворачиваются в пробелы (до `TAB_STOP`), а ячейки таблицы добиваются пробелами до ширины колонки + рамки `│` ? поэтому кап достигается раньше, чем 255 ?полезных? символов. > **RAW-режим (`F2`) этого лимита не имеет** ? он рисует прямо из файла > побайтово, длинные строки видны целиком (через wrap `F3` или гориз. скролл). **Идея снятия лимита** (оценка, не реализовано) ? расширить длину до `uint16_t`: - `cache_rec_t.len` `uint8_t?uint16_t` ? структура остаётся **ровно 8 байт** (len съедает один pad-байт), адресация `idx<<3` не меняется. Бесплатно. - `g_ncells` / `g_ncells_at_space` ? `uint16_t` ? главная цена по **коду/скорости**: 16-битная арифметика на Z80 в горячем `gc_put` (вызов на каждую ячейку) и в scan-циклах. Ориентир: **+0.2?0.4 КБ кода** + замедление индексации. - Буфер `g_cells[MAX_CACHE_LINE_LEN*2]` в near-RAM (W2) ? главная цена по **памяти**: `2 ? кап` байт. Сейчас 510 Б; кап 512 ? +0.5 КБ, 1024 ? +1.5 КБ. EMM-кэш контента (1 МБ) длинные строки тянет легко ? узкое место именно near. - `viewport_x` (и копия в `docset_t`) + потолок `max_vx` (сейчас 248) ? `uint16_t`, иначе хранить >255 можно, а доскроллить нельзя. Плюс `widths[]`/`ccx` в таблицах/коде. Реалистичный компромисс ? кап 512?1023: хватит почти всем листингам/таблицам, цена ~+0.3 КБ кода и +0.5?1.5 КБ near-RAM. **Дешёвая полумера без `uint16_t`** ? ставить честный маркер обрезки на 255-й позиции, чтобы потеря была видна. ## TODO 1. **Увеличение размера документов.** Снять лимит 128 КБ: Достаточно разрешить работать с большим кол-вом страниц памяти, пока оттестированно на работе с 8-мю страницами по 16Кб. Сделано: форматированные таблицы с рамкой; поддержка кодировок CP866 / CP1251 / KOI8-R / UTF-8 с автоопределением и переключением по `F8` (второй набор строится лениво, по первому переключению). 4. **Ускорение рендеринга.** Кэш строк экрана. Оптимизация цикла вывода символов через BIOS WRCHAR (пакетный вывод, DMA). --- *MDView v0.2 · (c) 2026 Петров А.Г.* --- # Sprinter C Compiler ? v1.0 C toolchain for **Sprinter** ? the Z80-based home computer by Peters Plus, running ESTEX DSS. Host: macOS / Linux. Target: `.EXE` files in SprintEXE format. Built on top of **SDCC 4.5** (vendored in `third_party/sdcc/`). This repository adds everything Sprinter-specific: crt0, linker integration, libc wrappers over ESTEX, banked-call trampolines, graphics & accelerator API, mouse driver wrappers, and the `mkexe` utility for producing SprintEXE images. ## What you get * **`bin/sprinter-cc`** ? one-line driver: `sprinter-cc -o foo.exe foo.c` * **Memory modes**: `tiny`, `small`, `big`, `huge`, `manual` ? see below. * **stdio + conio**: printf, puts, putchar, getchar, fopen/fread/..., cprintf, cputs, putch, textcolor/textbackground/textattr, gotoxy, kbhit/getch. * **Graphics**: 320?256?256 and 640?256?16 modes, accelerator-backed primitives (hline / vline / rect / fill_rect / line via Bresenham, plus clear), bitmap-font text in both modes via BIOS character generator. * **File I/O**: POSIX (`open`/`read`/`write`/`close`/`lseek`/`unlink`/`creat`), FILE\* streams (`fopen`/`fgets`/`fwrite`/...), directory listing (`ffirst`/`fnext`), `chdir`/`getcwd`/`mkdir`/`rmdir`, `stat`/`fstat`. * **Memory**: 32 KB heap (W2-resident), banking-aware page allocator (`mem_alloc_pages`/`bank_read`/`bank_write`), explicit memory modes for sub-16 KB programs. * **Mouse**: full Sprinter driver wrapper (14 functions including custom cursor bitmaps). * **Environment**: `getenv`/`putenv`/`sysenv` over ESTEX `$46`. * **Time**: `getdatetime`/`setdatetime` + POSIX `time`/`localtime`/`mktime`/`asctime`/`ctime`. * **Misc**: `errno`/`strerror`/`perror`, `atexit`, `setjmp`/`longjmp`, `sleep`, full argv parsing in crt0. ## Quick start ```sh git clone sprinter-c cd sprinter-c make sdcc # one-time: fetch SDCC 4.5 binary (~25 MB) make all # build mkexe + libsprinter.lib + 27 examples make floppy # pack everything into mame/v306/IMG/mc.img cd mame/v306 && ./run_mame.sh # boot Sprinter in MAME ``` Compile a single program: ```sh cat > hello.c < int main(void) { puts("Hello, Sprinter!"); return 0; } EOF bin/sprinter-cc -o hello.exe hello.c ``` That's it ? `hello.exe` is now a valid SprintEXE you can `RUN HELLO` from the ESTEX shell. ## Memory modes Sprinter's address space is four 16 KB windows (W0 / W1 / W2 / W3). DSS allocates pages by program size ? small programs get only one page. Pick a memory mode based on what your program needs: | Mode | Code lives in | Banking | Use when | Note | |---|---|---|---|---| | `tiny` (default) | W2 (0x8100+) | no | code+data < 14 KB | | | `small` | W1-W2 (0x4100+) | no | code+data < 30 KB | | | `big` | W2 + W1 banking | yes (W1) | tiny + extra code modules | | | `huge` | W1-W2 + W3 banking | yes (W3) | small + extra code modules | | | `manual` | user-specified | optional | special layouts | Not implemented | ```sh sprinter-cc --memory small -o big.exe bigprog.c sprinter-cc --memory huge -o app.exe main.c --bank 1=engine.c --bank 2=ai.c ``` Banked functions are declared with `__banked`: ```c void engine_tick(int dt) __banked; // lives in BANK1, automatically swapped ``` ## Examples (27 total) | Example | What it demonstrates | |---|---| | `hello` | Hello world with stdio + conio Turbo-C-style colors | | `argv` | argv parsing in crt0 | | `cat` | File I/O ? read & print TEST.TXT | | `seek` | 32-bit lseek over a 100 KB file | | `ls` | Directory listing via ffirst/fnext | | `filetest` | FILE\* streams (fopen/fread/...) | | `stattest` | `stat`/`fstat` on files and directories | | `errno` | errno / strerror / perror | | `mem_test` | Page allocator + bank\_read/bank\_write | | `malloc` | Heap stress test (200+ allocations) | | `banked` | Banked code in W3 (huge mode) | | `bankedbg` | Banked code in W1 (big mode) | | `banklocl` | Bank-local static data and BSS | | `mouse` | Mouse driver in text mode | | `gfx_mous` | Mouse with custom bitmap cursor in graphics mode | | `gfx_demo` | 320?256?256 lines / rects / accelerator | | `gfx_d16` | 640?256?16 same primitives | | `gfx_text` | Bitmap-font text on graphics screen | | `timedir` | Date/time + directory listing | | `ptime` | POSIX time API | | `openenv` | open() flags + environment vars | | `conio` | conio API smoke test | | `attrprob` | Probe Sprinter text attribute byte layout | | `strtest` | string.h test (from SDCC's z80.lib) | | `stdlib` | stdlib.h test (qsort / rand / strtol / etc.) | | `assrtest` | assert() | | `rt_test` | Runtime helpers (sleep, setjmp, atexit) | ## Headers Standard: * `` ? puts / printf / FILE\* + Sprinter-specific dec/hex helpers * `` ? atoi / atof / malloc / qsort / ... (from SDCC z80.lib) * `` / `` / `` ? from SDCC z80.lib * `` ? read / write / close / lseek / unlink * `` ? open / creat + O\_RDONLY / O\_CREAT / ... * `` ? errno + error names + strerror * `` ? stat / fstat * `` / `` ? from SDCC Sprinter-specific: * `` ? putch / cputs / cprintf, textcolor / textbackground / textattr, kbhit / getch, clrscr, gotoxy, wherex/y * `` ? gfx_init/done, palette, putpixel, hline/vline/rect/fill_rect/line, text ? for both 320 and 640 modes (gfx_\*16 variants) * `` ? full 14-function driver wrapper + mouse_cursor_t with bitmap support * `` ? chdir / getcwd / mkdir / rmdir / ffirst / fnext / ffblk * `` ? getdatetime / setdatetime + POSIX time / localtime / etc. * `` ? raw ports, ESTEX/BIOS function numbers, env API * `` ? exit / \_exit / atexit * `` ? mem\_alloc\_pages / mem\_free\_block / bank\_read / bank\_write * `` ? Solid-C compatibility layer (aliases + BOOL/WORD/uint types) ## Toolchain commands ```sh make all # build mkexe + lib + every example make floppy # repack mame/v306/IMG/mc.img with all .exe files make check # 17 mkexe unit-tests make clean # remove all build artefacts make sdcc # one-time: fetch SDCC 4.5 binary ``` ### sprinter-cc options ``` sprinter-cc -o foo.exe foo.c [more.c ...] [options] --memory MODE tiny | small | big | huge | manual (default: tiny) --memory-manual SPEC explicit placement (CODE=W1|W2,DATA=W1|W2|SAME,BANKED=W1|W3) --stack-size N bytes reserved for the stack (default ~1278) --crt0=TYPE default | minimal | banked | small --bank N=FILE.c compile FILE.c into bank N (repeatable, max 15) --debug enable runtime diagnostics (defines DEBUG_RT) -I PATH extra include path -L 0xADDR / -E / -S override load / entry / stack addresses -Wl FLAG pass FLAG to sdldz80 --mkexe FLAG pass FLAG to mkexe (e.g. --mkexe -p --mkexe 0 for bank padding) -v verbose ``` ## Status What works in v1.0: * Compile / link / pack to SprintEXE ? verified on all 27 examples * Four memory modes (tiny / small / big / huge) * Graphics (both modes) with accelerator * Mouse (text + graphics cursor) * File I/O, directories, environment, time * All headers listed above Deferred to v2.0 (see `docs/TODO.md`): * **Turbo-C-style BGI graphics API** ? `initgraph` / `setcolor` / `circle` / `getimage` / `putimage` / etc. on top of our `gfx_*` primitives * Remaining Solid-C compatibility gaps (Phase 2/3) ? see `docs/solid_c_compatibility.md` * Manual memory mode * Rewrite FILE\* stream API (current implementation is very primitive and doesn't use buffers) Deferred to v3.0: * **IM2 interrupt handlers** ? research complete (`docs/im2_isr_design.md`), implementation scheduled for v3 * **Audio API** (AY-3-8910 + COVOX) ? requires IM2 * **ISA-8 slot drivers** ? requires IM2 (???) ## Documentation * `docs/TODO.md` ? roadmap and open work items * `docs/solid_c_compatibility.md` ? gap analysis vs Solid-C 2004 * `docs/im2_isr_design.md` ? interrupt handler design (v2) * `docs/converted/` ? source documentation (ESTEX, BIOS, architecture) converted to plain text for `grep` * `docs/reference/`, `docs/samples/`, `docs/memory management/` ? original Russian docs and code samples from Peters Plus ## Repository layout ``` bin/sprinter-cc one-line compiler driver (bash) toolchain/mkexe/ host-side tool: .ihx -> .exe SprintEXE toolchain/check_banks.py post-link bank size enforcer runtime/ crt0 variants (default, minimal, small, banked) bank trampolines, heap, heap_top libc/include/ headers libc/io|stdio|mem|gfx/ C and asm sources for libsprinter.lib lib/ Makefile that archives libsprinter.lib via sdar examples/ 27 example programs mame/v306/ MAME binary + Sprinter ROM/HDD images + floppy script third_party/sdcc/ vendored SDCC 4.5 (fetched via `make sdcc`) third_party/solid-c/ reference: original Sprinter native C (for compat target) docs/ documentation ``` ## License This repository contains: * Original code in `bin/`, `toolchain/`, `runtime/`, `libc/`, `lib/`, `examples/` ? MIT-licensed. * `third_party/sdcc/` ? SDCC 4.5 under GPLv2 with linking exception (see `third_party/sdcc/COPYING.txt`) * `third_party/solid-c/` ? original Sprinter Solid C, used only as a reference ## Credits * **Sprinter / Peters Plus** ? Иван Мак, Дмитрий Паринов and the original team * **SDCC** ? for the underlying Z80 compiler * **MAME** ? for the Sprinter Sp2000 emulation --- For questions / patches: see CONTRIBUTING.md (TBD) or open an issue. --- # 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` - Полный `` (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:` строки) из вывода `sdcc`. Через `-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 ? количество строк/столбцов экономии. Применимо ко всем композитным примитивам. # MDView ? Просмотрщик Markdown для Sprinter **MDView** ? программа для просмотра документов в формате *Markdown* на компьютере Sprinter (процессор Z80). Документ хранится в отдельном W3 окне и не занимает основную RAM программы. ## Возможности - Документы до **128 КБ** (8 страниц EMM по 16 КБ каждая) - До **16 384** экранных строк в индексе - Автоматический перенос слов по ширине экрана (80 столбцов) - Горизонтальный сдвиг для широких строк (блоки кода, таблицы) - Статус-бар: имя файла, диапазон строк, процент прокрутки - Спиннер в строке состояния во время загрузки и индексации - Поддержка ?мягкого? склеивания строк в абзацах и цитатах ## Запуск ``` mdview [имя_файла.md] ``` Если имя файла не задано, загружается `README.MD`. ## Управление ``` Клавиша Действие ───────────── ──────────────────────────────────────── Up Down Прокрутка на одну строку вверх / вниз PgUp PgDn Прокрутка на страницу (30 строк) Home Начало документа End Конец документа Left Right Горизонтальный сдвиг (только nowrap-строки) F1 Окно справки F8 Кодировка: CP866 ? CP1251 ? KOI8-R ? UTF-8 ? ... F10 / Esc Выход из программы ``` Кодировка определяется автоматически при открытии (BOM + эвристика по первым 4 КБ); `F8` переключает её вручную, если детекция ошиблась. 8-битные кодировки (CP866/CP1251/KOI8-R) переключаются мгновенно (ремап на отрисовке). Второй набор индекс/кэша (например UTF-8) строится лениво ? при первом переключении в него (короткая пауза со спиннером), дальше мгновенно. ## Синтаксис Markdown ### Заголовки Поддерживаются уровни H1?H4. Уровни H5 и H6 отображаются как H4. # Заголовок первого уровня ## Заголовок второго уровня ### Заголовок третьего уровня #### Заголовок четвёртого уровня ### Текстовое форматирование **Жирный текст** выделяется двойными звёздочками: `**текст**` *Курсив* выделяется одиночными звёздочками `*текст*` или знаком подчёркивания `_текст_` `Встроенный код` обозначается обратными кавычками ~~Зачёркнутый текст~~ ? двойные тильды: `~~текст~~` ### Ненумерованный список Маркеры `-`, `*` или `+`: - Первый пункт списка - Второй пункт списка - Третий пункт с достаточно длинным текстом, который при необходимости будет перенесён на следующую строку с сохранением отступа ### Нумерованный список 1. Первый элемент 2. Второй элемент 3. Третий элемент ### Цитата > Блок цитаты начинается с символа `>`. Несколько последовательных > строк одной цитаты склеиваются в единый абзац с автоматическим > переносом слов. ### Блок кода (verbatim) Блок кода заключается в тройные обратные кавычки. Внутри блока текст отображается ?как есть? без разбора Markdown: ``` #include #include int main(void) { puts("Hello, Sprinter!"); return 0; } ``` ### Горизонтальная линия Три или более символов `---`, `***` или `___` на отдельной строке: --- ## Технические характеристики - **Платформа:** Sprinter, процессор Z80 @ 21 МГц - **Кодировки:** CP866 / CP1251 / KOI8-R / UTF-8 (автоопределение, `F8`) - **Максимальный размер файла:** 128 КБ - **Максимальное число строк в индексе:** 16 384 - **Режим памяти:** small - Код программы, cтек, данные, куча ? окнa W1-W2 (32 КБ, адреса 0x4000?0xBFFF). - Буфер файла ? страницы EMM, отображаемые в W3 (0xC000?0xFFFF) ## TODO 1. **Увеличение размера документов.** Снять лимит 128 КБ: Достаточно разрешить работать с большим кол-вом страниц памяти, пока оттестированно на работе с 8-мю страницами по 16Кб. Сделано: форматированные таблицы с рамкой; поддержка кодировок CP866 / CP1251 / KOI8-R / UTF-8 с автоопределением и переключением по `F8` (второй набор строится лениво, по первому переключению). 4. **Ускорение рендеринга.** Кэш строк экрана. Оптимизация цикла вывода символов через BIOS WRCHAR (пакетный вывод, DMA). --- *MDView v0.2 · (c) 2026 Петров А.Г.* --- # Sprinter C Compiler ? v1.0 C toolchain for **Sprinter** ? the Z80-based home computer by Peters Plus, running ESTEX DSS. Host: macOS / Linux. Target: `.EXE` files in SprintEXE format. Built on top of **SDCC 4.5** (vendored in `third_party/sdcc/`). This repository adds everything Sprinter-specific: crt0, linker integration, libc wrappers over ESTEX, banked-call trampolines, graphics & accelerator API, mouse driver wrappers, and the `mkexe` utility for producing SprintEXE images. ## What you get * **`bin/sprinter-cc`** ? one-line driver: `sprinter-cc -o foo.exe foo.c` * **Memory modes**: `tiny`, `small`, `big`, `huge`, `manual` ? see below. * **stdio + conio**: printf, puts, putchar, getchar, fopen/fread/..., cprintf, cputs, putch, textcolor/textbackground/textattr, gotoxy, kbhit/getch. * **Graphics**: 320?256?256 and 640?256?16 modes, accelerator-backed primitives (hline / vline / rect / fill_rect / line via Bresenham, plus clear), bitmap-font text in both modes via BIOS character generator. * **File I/O**: POSIX (`open`/`read`/`write`/`close`/`lseek`/`unlink`/`creat`), FILE\* streams (`fopen`/`fgets`/`fwrite`/...), directory listing (`ffirst`/`fnext`), `chdir`/`getcwd`/`mkdir`/`rmdir`, `stat`/`fstat`. * **Memory**: 32 KB heap (W2-resident), banking-aware page allocator (`mem_alloc_pages`/`bank_read`/`bank_write`), explicit memory modes for sub-16 KB programs. * **Mouse**: full Sprinter driver wrapper (14 functions including custom cursor bitmaps). * **Environment**: `getenv`/`putenv`/`sysenv` over ESTEX `$46`. * **Time**: `getdatetime`/`setdatetime` + POSIX `time`/`localtime`/`mktime`/`asctime`/`ctime`. * **Misc**: `errno`/`strerror`/`perror`, `atexit`, `setjmp`/`longjmp`, `sleep`, full argv parsing in crt0. ## Quick start ```sh git clone sprinter-c cd sprinter-c make sdcc # one-time: fetch SDCC 4.5 binary (~25 MB) make all # build mkexe + libsprinter.lib + 27 examples make floppy # pack everything into mame/v306/IMG/mc.img cd mame/v306 && ./run_mame.sh # boot Sprinter in MAME ``` Compile a single program: ```sh cat > hello.c < int main(void) { puts("Hello, Sprinter!"); return 0; } EOF bin/sprinter-cc -o hello.exe hello.c ``` That's it ? `hello.exe` is now a valid SprintEXE you can `RUN HELLO` from the ESTEX shell. ## Memory modes Sprinter's address space is four 16 KB windows (W0 / W1 / W2 / W3). DSS allocates pages by program size ? small programs get only one page. Pick a memory mode based on what your program needs: | Mode | Code lives in | Banking | Use when | Note | |---|---|---|---|---| | `tiny` (default) | W2 (0x8100+) | no | code+data < 14 KB | | | `small` | W1-W2 (0x4100+) | no | code+data < 30 KB | | | `big` | W2 + W1 banking | yes (W1) | tiny + extra code modules | | | `huge` | W1-W2 + W3 banking | yes (W3) | small + extra code modules | | | `manual` | user-specified | optional | special layouts | Not implemented | ```sh sprinter-cc --memory small -o big.exe bigprog.c sprinter-cc --memory huge -o app.exe main.c --bank 1=engine.c --bank 2=ai.c ``` Banked functions are declared with `__banked`: ```c void engine_tick(int dt) __banked; // lives in BANK1, automatically swapped ``` ## Examples (27 total) | Example | What it demonstrates | |---|---| | `hello` | Hello world with stdio + conio Turbo-C-style colors | | `argv` | argv parsing in crt0 | | `cat` | File I/O ? read & print TEST.TXT | | `seek` | 32-bit lseek over a 100 KB file | | `ls` | Directory listing via ffirst/fnext | | `filetest` | FILE\* streams (fopen/fread/...) | | `stattest` | `stat`/`fstat` on files and directories | | `errno` | errno / strerror / perror | | `mem_test` | Page allocator + bank\_read/bank\_write | | `malloc` | Heap stress test (200+ allocations) | | `banked` | Banked code in W3 (huge mode) | | `bankedbg` | Banked code in W1 (big mode) | | `banklocl` | Bank-local static data and BSS | | `mouse` | Mouse driver in text mode | | `gfx_mous` | Mouse with custom bitmap cursor in graphics mode | | `gfx_demo` | 320?256?256 lines / rects / accelerator | | `gfx_d16` | 640?256?16 same primitives | | `gfx_text` | Bitmap-font text on graphics screen | | `timedir` | Date/time + directory listing | | `ptime` | POSIX time API | | `openenv` | open() flags + environment vars | | `conio` | conio API smoke test | | `attrprob` | Probe Sprinter text attribute byte layout | | `strtest` | string.h test (from SDCC's z80.lib) | | `stdlib` | stdlib.h test (qsort / rand / strtol / etc.) | | `assrtest` | assert() | | `rt_test` | Runtime helpers (sleep, setjmp, atexit) | ## Headers Standard: * `` ? puts / printf / FILE\* + Sprinter-specific dec/hex helpers * `` ? atoi / atof / malloc / qsort / ... (from SDCC z80.lib) * `` / `` / `` ? from SDCC z80.lib * `` ? read / write / close / lseek / unlink * `` ? open / creat + O\_RDONLY / O\_CREAT / ... * `` ? errno + error names + strerror * `` ? stat / fstat * `` / `` ? from SDCC Sprinter-specific: * `` ? putch / cputs / cprintf, textcolor / textbackground / textattr, kbhit / getch, clrscr, gotoxy, wherex/y * `` ? gfx_init/done, palette, putpixel, hline/vline/rect/fill_rect/line, text ? for both 320 and 640 modes (gfx_\*16 variants) * `` ? full 14-function driver wrapper + mouse_cursor_t with bitmap support * `` ? chdir / getcwd / mkdir / rmdir / ffirst / fnext / ffblk * `` ? getdatetime / setdatetime + POSIX time / localtime / etc. * `` ? raw ports, ESTEX/BIOS function numbers, env API * `` ? exit / \_exit / atexit * `` ? mem\_alloc\_pages / mem\_free\_block / bank\_read / bank\_write * `` ? Solid-C compatibility layer (aliases + BOOL/WORD/uint types) ## Toolchain commands ```sh make all # build mkexe + lib + every example make floppy # repack mame/v306/IMG/mc.img with all .exe files make check # 17 mkexe unit-tests make clean # remove all build artefacts make sdcc # one-time: fetch SDCC 4.5 binary ``` ### sprinter-cc options ``` sprinter-cc -o foo.exe foo.c [more.c ...] [options] --memory MODE tiny | small | big | huge | manual (default: tiny) --memory-manual SPEC explicit placement (CODE=W1|W2,DATA=W1|W2|SAME,BANKED=W1|W3) --stack-size N bytes reserved for the stack (default ~1278) --crt0=TYPE default | minimal | banked | small --bank N=FILE.c compile FILE.c into bank N (repeatable, max 15) --debug enable runtime diagnostics (defines DEBUG_RT) -I PATH extra include path -L 0xADDR / -E / -S override load / entry / stack addresses -Wl FLAG pass FLAG to sdldz80 --mkexe FLAG pass FLAG to mkexe (e.g. --mkexe -p --mkexe 0 for bank padding) -v verbose ``` ## Status What works in v1.0: * Compile / link / pack to SprintEXE ? verified on all 27 examples * Four memory modes (tiny / small / big / huge) * Graphics (both modes) with accelerator * Mouse (text + graphics cursor) * File I/O, directories, environment, time * All headers listed above Deferred to v2.0 (see `docs/TODO.md`): * **Turbo-C-style BGI graphics API** ? `initgraph` / `setcolor` / `circle` / `getimage` / `putimage` / etc. on top of our `gfx_*` primitives * Remaining Solid-C compatibility gaps (Phase 2/3) ? see `docs/solid_c_compatibility.md` * Manual memory mode * Rewrite FILE\* stream API (current implementation is very primitive and doesn't use buffers) Deferred to v3.0: * **IM2 interrupt handlers** ? research complete (`docs/im2_isr_design.md`), implementation scheduled for v3 * **Audio API** (AY-3-8910 + COVOX) ? requires IM2 * **ISA-8 slot drivers** ? requires IM2 (???) ## Documentation * `docs/TODO.md` ? roadmap and open work items * `docs/solid_c_compatibility.md` ? gap analysis vs Solid-C 2004 * `docs/im2_isr_design.md` ? interrupt handler design (v2) * `docs/converted/` ? source documentation (ESTEX, BIOS, architecture) converted to plain text for `grep` * `docs/reference/`, `docs/samples/`, `docs/memory management/` ? original Russian docs and code samples from Peters Plus ## Repository layout ``` bin/sprinter-cc one-line compiler driver (bash) toolchain/mkexe/ host-side tool: .ihx -> .exe SprintEXE toolchain/check_banks.py post-link bank size enforcer runtime/ crt0 variants (default, minimal, small, banked) bank trampolines, heap, heap_top libc/include/ headers libc/io|stdio|mem|gfx/ C and asm sources for libsprinter.lib lib/ Makefile that archives libsprinter.lib via sdar examples/ 27 example programs mame/v306/ MAME binary + Sprinter ROM/HDD images + floppy script third_party/sdcc/ vendored SDCC 4.5 (fetched via `make sdcc`) third_party/solid-c/ reference: original Sprinter native C (for compat target) docs/ documentation ``` ## License This repository contains: * Original code in `bin/`, `toolchain/`, `runtime/`, `libc/`, `lib/`, `examples/` ? MIT-licensed. * `third_party/sdcc/` ? SDCC 4.5 under GPLv2 with linking exception (see `third_party/sdcc/COPYING.txt`) * `third_party/solid-c/` ? original Sprinter Solid C, used only as a reference ## Credits * **Sprinter / Peters Plus** ? Иван Мак, Дмитрий Паринов and the original team * **SDCC** ? for the underlying Z80 compiler * **MAME** ? for the Sprinter Sp2000 emulation --- For questions / patches: see CONTRIBUTING.md (TBD) or open an issue. --- # 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` - Полный `` (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:` строки) из вывода `sdcc`. Через `-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 ? количество строк/столбцов экономии. Применимо ко всем композитным примитивам.