Volkov: добавить Sprinter Commander

Реализовать двухпанельный Commander от платформенного PoC до этапов P6-P20: EMM-каталог, сортировку и выбор, операции с файлами и деревьями, транзакционное копирование, метаданные, политику конфликтов и предварительную проверку свободного места.

Добавить проектную документацию, HDD/MAME-сценарии и проверенные артефакты. Расширить libc операцией bank_write_page, исправлением режима O_RDONLY и связанными регрессионными проверками.
This commit is contained in:
2026-09-10 10:45:30 +03:00
parent 05bcd8197e
commit 8e389c03f8
870 changed files with 21310 additions and 25 deletions
@@ -0,0 +1,270 @@
# Sprinter Commander: экранный backend P2
Статус: ядро P2.1 и многоцветная тема P2.2 прошли MAME; аппаратная проверка,
восстановление после child и измерение времени ожидаются.
Связанные документы:
[архитектура](commander-architecture.md),
[требования](commander-requirements.md),
[roadmap](commander-roadmap.md),
[результаты P1](p1-platform-probes.md),
[результаты P2.1](p2-screen-probe-results.md),
[результаты P2.2](p2-palette-results.md)
## 1. Решение
Commander не использует систему оконных дескрипторов BIOS (`WIN_OPEN`,
`WIN_CLOSE`, `WIN_COPY_WIN`, `WIN_RESTORE_WIN`). Текущий BIOS хранит только
один описатель с идентификатором 0; открытие нового окна уничтожает прежнее
состояние. Диалоги, панели и меню поэтому являются прямоугольниками внутри
собственной модели экрана Commander, а не окнами BIOS.
Целевой backend выводит в координаты полного экрана через системные функции,
но не открывает и не переключает BIOS-окна. Основной пакетный примитив —
ESTEX `WINREST`; для специальных операций доступны координатные `WRCHAR`,
`CLEAR` и `SCROLL`. Прямой доступ к VRAM не используется.
## 2. Что уже доказано
P1 и `examples/mdview2` подтвердили формат логического буфера:
```text
(char, attr), по строкам, stride = width * 2, без padding
```
`WINREST` корректно выводит полный экран, отдельную строку и прямоугольник из
ненулевого смещения EMM-страницы. Эта функция ESTEX не создаёт BIOS-окно и не
использует его идентификатор; слово `WIN` здесь означает прямоугольную область
текущего экрана.
`mdview2` — полезный образец полноэкранного приложения: состояние документа
принадлежит программе, строки материала выводятся из собственного кэша, а
status/menu рисуются координатными примитивами. Commander сохраняет этот
принцип, но выносит способ вывода в отдельный backend.
## 3. Почему используем системный координатный вывод
- DSS/BIOS уже знают реальное устройство текстового режима, знакогенератора,
активной screen page и системной палитры.
- `WINREST` выводит сразу прямоугольник из EMM, поэтому не требуется RST на
каждую ячейку.
- Координатные ESTEX-функции не используют идентификатор глобального окна.
- После возврата из child достаточно снова установить 80x32 и вывести всю
экранную модель.
- Один путь работает и в MAME, и на реальном Sprinter без предположений о
внутренней раскладке VRAM.
## 4. Граница между оконными и безоконными функциями
Запрещены функции управления глобальными окнами, где передаётся window ID:
`WIN_OPEN`, `WIN_CLOSE`, `WIN_COPY_WIN`, `WIN_RESTORE_WIN`, `WIN_MOVE_WIN`.
Они зависят от единственного поддерживаемого описателя 0.
Разрешены:
- ESTEX `WINREST 5Ah` — прямоугольник `(row, col, height, width)`, EMM page
и source address, без window ID;
- ESTEX `WRCHAR 58h`, `CLEAR 56h`, `SCROLL 55h` — абсолютные координаты
активной screen page;
- BIOS `LP_SET_PLACE`/`LP_PRINT_*`/`LP_CLS_WIN*` — только как пакетные
оптимизации для глобального экрана, без открытия новых окон.
Системные вызовы чтения и записи текстовой палитры также разрешены. Их
отсутствие в P2.1 — способ изолировать экранный backend, а не архитектурный
запрет. Выделенный модуль темы использует их для полного save/set/restore;
контракт подтверждён P2.2.
Чтобы renderer не зависел от текущего cursor/place, основным остаётся
`WINREST`. Если применяется BIOS `LP_*`, backend всегда сам устанавливает
place непосредственно перед операцией и не оставляет его частью состояния UI.
## 5. Палитра и FLASH
У Sprinter атрибут — индекс в четыре текстовых палитровых плана: paper, ink,
flash-paper и flash-ink. Сигнал FLASH переключает пары глобально. Чтобы тема
Commander не мигала, для каждого используемого атрибута normal-paper и
flash-paper задаются одинаково, как и normal-ink с flash-ink.
Первый pattern P1 с raw-атрибутами `0x11..0x17` действительно содержал
blue-on-blue. Это ошибка выбора атрибутов, а не нестабильность палитры;
арифметическое построение атрибутов для UI поэтому по-прежнему запрещено.
Прежняя гипотеза о пропадающих цветных строках была вызвана неверным
предпросмотром PNG. Проверка декодированных файлов показала, что именованные
цветные кадры P1 и три диагностических кадра P2.1 пиксельно идентичны.
P2.2 отдельно проверил шесть цветовых атрибутов:
- сохранены все 24 исходные записи (6 атрибутов × 4 плана);
- установлены одинаковые normal/FLASH-пары;
- обратное чтение дало `PASS (0 mismatches)`;
- пять временных кадров имеют одинаковый viewport Sprinter;
- перед выходом исходные записи восстановлены.
Единственное изменение между двумя полными PNG находилось в служебной панели
MAME: 42 пикселя LED `Drive A`. Элементы layout вне viewport Sprinter в
экранных сравнениях не учитываются. Подробности — в
[результатах P2.2](p2-palette-results.md).
## 6. Интерфейс backend
Минимальный интерфейс не раскрывает ESTEX/BIOS остальным модулям:
```c
int sc_video_init(void);
void sc_video_shutdown(void);
int sc_video_present_full(uint8_t emm_page);
int sc_video_present_rect(uint8_t emm_page, uint16_t offset,
uint8_t row, uint8_t col,
uint8_t height, uint8_t width);
int sc_video_scroll_rect(uint8_t row, uint8_t col,
uint8_t height, uint8_t width,
int8_t delta);
```
`sc_screen` строит `(char, attr)` в EMM и вызывает backend. Он не вызывает
`WIN_OPEN`, `WRCHAR`, `WINREST` или BIOS непосредственно. Единственная
реализация PoC — `sc_video_system.c`; выбор конкретного системного примитива
остаётся внутри неё.
## 7. Вертикальный текстовый scroll
`mdview2` использует эффективную схему для перемещения на одну строку:
```text
Down: scroll(rect, UP) → нарисовать новую нижнюю строку
Up: scroll(rect, DOWN) → нарисовать новую верхнюю строку
PgUp/PgDn/Home/End → полный redraw области
```
Его viewport — `(col=0, row=1, width=80, height=30)`. Вызов libc
`scroll()` обращается к ESTEX `SCROLL 55h`, который сдвигает прямоугольник
активной screen page на одну строку. Он не использует идентификатор окна
BIOS и совместим с решением не применять window descriptors.
Исходники `mdview2` и контракт ESTEX не доказывают, что операция реализована
аппаратным регистром видеоконтроллера: для документации это **системный
прямоугольный scroll**, пока не появится измерение или исходник DSS. Возможный
глобальный аппаратный сдвиг начала экрана для Commander всё равно неудобен —
он одновременно сдвинет рамки и обе панели. Реализация
`sc_video_scroll_rect()` делегирует прямоугольник ESTEX; это не создаёт
зависимости от BIOS-окон.
Для Commander эта оптимизация применяется только когда `top` панели
изменился ровно на один. Прямоугольники — внутренности панелей без рамок:
```text
left: col=1, row=1, width=38, height=27
right: col=41, row=1, width=38, height=27
```
Точные размеры уточняются renderer-геометрией, но центральный разделитель,
заголовок, footer и пассивная панель не должны входить в scroll rectangle.
Экранный EMM-буфер остаётся источником истины. Поэтому
`sc_screen_scroll_rows()` сначала сдвигает соответствующие `(char, attr)` в
модели и строит открытую строку, затем вызывает `sc_video_scroll_rect()` и
выводит эту строку. Системный scroll без синхронизации модели запрещён:
после диалога или EXEC полный redraw иначе вернул бы старые строки.
Fallback при любой неопределённости — present всех 27 строк активной панели.
P2.1 должен проверить четыре границы прямоугольника, оба направления, верх и
низ списка, 1000 последовательных scroll и неизменность соседней панели.
## 8. Псевдографика и CP866
`mdview2` подтверждает стандартный light box-drawing набор CP866/CP437:
| Имя | Код | Глиф | Имя | Код | Глиф |
|---|---:|:---:|---|---:|:---:|
| `SC_G_H` | `C4` | ─ | `SC_G_V` | `B3` | │ |
| `SC_G_TL` | `DA` | ┌ | `SC_G_TR` | `BF` | ┐ |
| `SC_G_BL` | `C0` | └ | `SC_G_BR` | `D9` | ┘ |
| `SC_G_TM` | `C2` | ┬ | `SC_G_BM` | `C1` | ┴ |
| `SC_G_ML` | `C3` | ├ | `SC_G_MR` | `B4` | ┤ |
| `SC_G_X` | `C5` | ┼ | | | |
Стрелки для подсказок, если понадобятся: Up=`18`, Down=`19`, Right=`1A`,
Left=`1B`. Это управляющий диапазон ASCII, поэтому такие байты нельзя
передавать через API, интерпретирующий CR/LF/BEL/Esc. Они кладутся как raw
glyph непосредственно в `ScCell`.
Правила Commander:
- все коды сосредоточены в `sc_glyphs.h`, чисел `0xB3` по renderer-модулям
быть не должно;
- исходная рамка строится из глифов, а не из UTF-8-литералов;
- строки UI хранятся в CP866 или проходят явную build-time конвертацию;
- border test рисует все 11 соединений и четыре стрелки;
- атрибут каждого глифа задаётся через `COLOR(fg, bg)`;
- при координатном raw-выводе символы `<0x20` считаются глифами, а не
командами.
Для PoC достаточно одинарных рамок. Двойные рамки VC-класса добавляются
после проверки полного набора соединений, чтобы не смешивать несовместимые
single/double junctions.
Расширенный набор, уже подтверждённый таблицей конвертации `mdview2`, можно
зарезервировать для прогресса, отметок и будущих тем:
| Имя | Код | Глиф | Назначение |
|---|---:|:---:|---|
| `SC_G_SHADE_LIGHT` | `B0` | ░ | светлая заливка |
| `SC_G_SHADE_MEDIUM` | `B1` | ▒ | средняя заливка |
| `SC_G_SHADE_DARK` | `B2` | ▓ | тёмная заливка |
| `SC_G_BLOCK` | `DB` | █ | полный блок |
| `SC_G_HALF_UP` | `DF` | ▀ | верхняя половина |
| `SC_G_HALF_DOWN` | `DC` | ▄ | нижняя половина |
| `SC_G_HALF_LEFT` | `DD` | ▌ | левая половина |
| `SC_G_HALF_RIGHT` | `DE` | ▐ | правая половина |
| `SC_G_BULLET` | `F9` | ∙ | маркер |
| `SC_G_CHECK` | `FB` | √ | отметка |
| `SC_G_SQUARE` | `FE` | ■ | квадрат |
`mdview2_help.c` рисует рамку этими кодами через `wrchar()` и BIOS fill-
функции. Commander заимствует таблицу глифов и геометрию, но обращается к ним
только через `ScCell`/`sc_video`: текущий cursor/place не становится частью
модели интерфейса.
## 9. Coordinate-screen probe P2.1
Отдельный `tests/p2_screen/p2screen.exe` выполнен в MAME 0.287 / BIOS 3.06:
1. Режим 80x32 и одна EMM-страница экранной модели — `PASS`.
2. Полный экран одним `WINREST` с полной сверкой через `RDCHAR``PASS`.
3. Прямоугольник 20x2 из offset `0x2000``PASS`.
4. Две рамки и таблица CP866-глифов — `PASS` визуально и по `RDCHAR`.
5. Левая панель вверх и правая панель вниз — `PASS`; рамки и соседние клетки
совпали с моделью.
6. Синхронизация EMM-модели после системного `SCROLL``PASS`.
7. 1000 чередующихся однострочных scroll с итоговой полной сверкой — `PASS`.
8. Сохранение W3 вокруг `WINREST`, `RDCHAR` и `SCROLL``PASS`.
9. Один системный атрибут стабилен в трёх временных фазах — `PASS`, кадры
пиксельно идентичны.
10. Шесть атрибутов и четыре палитровых плана — `PASS` отдельной пробой P2.2.
Остались отдельные проверки, не нужные для начала skeleton:
11. Повторить полную инициализацию после child, изменившего видеорежим.
12. Сравнить время полного, однострочного и scroll-вывода.
13. Повторить принятый сценарий на реальном Sprinter.
Подробные значения и кадры приведены в
[результатах P2.1](p2-screen-probe-results.md).
## 10. Критерий выбора
Системный backend принят для реализации skeleton по результату MAME, потому
что:
- совпадает с EMM-моделью по символам и атрибутам;
- изолирует палитру в модуле темы и сохраняет отображение W3;
- частичное обновление не затрагивает соседние клетки;
- scroll прямоугольника не двигает рамку и пассивную панель;
- все псевдографические соединения совпадают с CP866;
- базовый системный атрибут стабилен во времени;
- код не обращается к `RGADR`, `RGMOD` и VRAM-банкам напрямую.
До аппаратного принятия backend остаются прогон на реальном Sprinter и
повторная инициализация после EXEC. Многоцветность подтверждена отдельным
критерием P2.2 и не выводится из результата P2.1.