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,678 @@
# Sprinter Commander: архитектура
Статус: P1/P2 подтверждены; одностраничная модель панели P3 реализована,
проходит граничные проверки DSS
Связанные документы:
[требования](commander-requirements.md),
[клавиши и команды](commander-keymap.md),
[план разработки](commander-roadmap.md),
[платформенные пробы P1](p1-platform-probes.md),
[экранный backend P2](p2-screen-backend.md),
[результаты P2.1](p2-screen-probe-results.md),
[результаты skeleton P2](p2-skeleton-results.md)
## 1. Цели архитектуры
Архитектура должна позволить получить небольшой технический PoC, не создавая
тупиков для полной версии. Главные ограничения задаются не исходным VC, а
платформой Sprinter:
- Z80 имеет 64-КБ адресное пространство;
- W0 занят DSS/BIOS;
- стек обязан находиться в W2;
- полный список двух панелей не помещается вместе с кодом в near-память;
- код полной версии потребует банков;
- DSS имеет критический предел в восемь открытых файлов;
- текстовый экран удобно восстанавливать непосредственно из EMM-страницы.
## 2. Принятые архитектурные решения
### ADR-001. Новая реализация, а не перенос
Commander пишется на C для SDCC. VC 4.05/4.99 используются как описание
поведения и состава функций, Dos Navigator — как источник архитектурных
идей. Зависимости от DOS interrupt API, сегментной модели x86, PSP, DTA,
TSR, overlays, XMS и EMS в код проекта не переносятся.
### ADR-002. Лёгкий цикл событий вместо оконного framework
Приложение имеет один цикл событий, набор символьных команд и явное состояние
экрана. Иерархия объектов наподобие Turbo Vision не используется.
Причины:
- меньше резидентного кода и данных;
- нет виртуальных методов и потоковой сериализации объектов;
- проще анализировать банковские вызовы;
- легче гарантировать освобождение ресурсов.
### ADR-003. Целевой режим памяти — `big`
PoC собирается с `--memory big --safe`:
- W0 `0x00000x3FFF` — DSS/BIOS;
- W1 `0x40000x7FFF` — переключаемые банки кода;
- W2 `0x80000xBFFF` — HOME, DATA/BSS, heap и стек;
- W3 `0xC0000xFFFF` — EMM-страницы данных.
Если первоначальный skeleton временно собирается без банков, это не меняет
контрактов модулей. До принятия PoC должна быть проверена именно сборка `big`.
### ADR-004. Панели, экран и рабочая область копирования хранятся в EMM
Начиная с P4 PoC выделяет один блок из четырёх последовательных 16-КБ страниц.
P2/P3 использовали первые три страницы; четвёртая заранее резервируется текущим
`ScApp`, чтобы копирование не увеличивало W2 и не меняло схему памяти во время
операции:
| Логическая страница | Назначение | Использование |
|---:|---|---|
| 0 | записи левой панели | до 640 `ScDirEntry`, 1024 байта резерва |
| 1 | записи правой панели | до 640 `ScDirEntry`, 1024 байта резерва |
| 2 | экранный буфер | первые 5120 байт |
| 3 | рабочая область copy job | первые 4096 байт в PoC |
Допускается выделение четырёх отдельных блоков, если эксперимент покажет
проблему с последовательным блоком, но внешний интерфейс хранилища от этого
не меняется.
### ADR-005. Запрещены долгоживущие указатели на W3
Указатель в диапазон `0xC0000xFFFF` действителен только внутри функции,
которая явно владеет отображением W3. После выхода из неё указатель считается
недействительным.
Ни `PanelState`, ни `ScApp`, ни длительная операция не содержат near-указатель
на содержимое EMM. Они хранят физическую страницу и смещение.
### ADR-006. Экран строится как модель ячеек
Каждая экранная ячейка представлена двумя байтами `(символ, атрибут)`. Формат
совпадает с подтверждённым форматом ESTEX `WINREST`:
```c
typedef struct {
uint8_t ch;
uint8_t attr;
} ScCell;
```
Полный экран занимает `80 * 32 * 2 = 5120` байт. Renderer изменяет EMM-буфер,
помечает строки грязными, а backend выводит либо диапазон строк, либо весь
экран. Backend использует координатные ESTEX/BIOS-функции; основной пакетный
путь — проверенный `WINREST`. Прямой доступ к VRAM не используется.
Renderer использует смысловые роли атрибутов (`frame`, `title`, `active`,
`passive`, `error`), а не числа палитры. Модуль `sc_theme`, подтверждённый
пробой P2.2, назначает ролям разные атрибуты, сохраняет затрагиваемые записи
четырёх планов и восстанавливает их при cleanup. Это не меняет формат
`ScCell` или API renderer.
Системные BIOS/ESTEX-вызовы палитры разрешены; запрещён только обход системного
экранного API через прямую адресацию VRAM и оконные дескрипторы BIOS.
Дескрипторы BIOS `WIN_OPEN/WIN_CLOSE/WIN_*_WIN` не используются: BIOS хранит
только один описатель окна 0. Слово `WIN` в ESTEX `WINREST` означает область
экрана и не делает эту функцию частью BIOS window manager.
### ADR-007. Источник файлов отделён от панели
Панель не вызывает `ffirst/fnext` напрямую. Она работает через модуль
источника. В PoC реализован только `SC_SOURCE_REAL_FS`.
Вместо таблицы виртуальных функций используется `kind` и обычный диспетчер:
```c
typedef enum {
SC_SOURCE_REAL_FS = 0,
SC_SOURCE_SEARCH,
SC_SOURCE_ARCHIVE
} ScSourceKind;
```
Такой подход не создаёт near/far function pointers и упрощает размещение
реализаций по банкам. `SEARCH` и `ARCHIVE` до соответствующих этапов должны
возвращать внутреннюю ошибку `SC_ERR_UNSUPPORTED`, которая на границе
платформы при необходимости отображается как ESTEX `EUNKOP`.
### ADR-008. Длительные операции являются jobs
Копирование, а позднее удаление, поиск и подсчёт размеров, оформляются как
конечные автоматы:
```text
INIT -> PREPARE -> RUN_CHUNK -> PRE_COMMIT -> COMMIT -> DONE
| | |
+-> CANCEL ----+-----------+
+-> ERROR -----+-----------+
```
Один шаг не должен надолго захватывать управление. Между блоками приложение
может обновить прогресс и проверить отмену. Даже последний успешно записанный
блок завершает только `RUN_CHUNK`: отдельный вызов переводит job через
`PRE_COMMIT` к атомарному rename. Поэтому `Esc`, полученный после отображения
`N/N`, всё ещё успевает закрыть и удалить temp до появления целевого файла.
### ADR-009. Viewer, editor и setup сначала отдельные программы
Основной Commander отвечает за панели и операции с файлами. Просмотрщик,
редактор и настройка первоначально запускаются через `EXEC` как отдельные
`.EXE`. Это уменьшает HOME, число банков и последствия ошибки компонента.
После 1.0 они могут быть интегрированы банками, если измерения покажут
реальную пользу.
## 3. Слои системы
```text
+------------------------------------------------------+
| app: main loop, mode, active panel, command dispatch |
+----------------------+-------------------------------+
| panels/model | UI/dialogs/status |
+----------------------+-------------------------------+
| sources | renderer/screen buffer |
+----------------------+-------------------------------+
| jobs: copy/... | storage: EMM/page access |
+----------------------+-------------------------------+
| platform: ESTEX, keyboard, exec, screen, filesystem |
+------------------------------------------------------+
| libc + Sprinter DSS/BIOS |
+------------------------------------------------------+
```
Зависимости направлены вниз. `platform` не знает о панелях; `storage` не
показывает сообщения; renderer не читает файловую систему.
## 4. Предлагаемая структура исходников
```text
Makefile
src/
sprcmd.c точка входа и аварийная очистка
sc_app.c главный цикл и состояние приложения
sc_command.c преобразование событий в команды и dispatch
sc_input.c клавиатурные события
sc_screen.c EMM framebuffer и dirty rows
sc_video_system.c координатный вывод ESTEX/BIOS без оконных ID
sc_draw.c рамки, строки, числа, сокращение путей
sc_panel.c состояние и поведение панели
sc_panel_draw.c представление панели
sc_dir_store.c записи каталога в EMM
sc_sort.c сравнение и сортировка записей
sc_source.c диспетчер источников
sc_source_fs.c настоящий каталог DSS
sc_copy_job.c PoC-копирование одного файла
sc_copy_ui.c банковая оркестрация F5 и прогресс
sc_dialog.c сообщения и подтверждения
sc_path.c безопасная работа с путями 8.3
sc_error.c нормализация errno/ESTEX ошибок
sc_platform_exec.c ESTEX EXEC/WAIT
sc_platform_screen.c wrappers WINREST/WRCHAR/SCROLL и screen mode
include/
sc_app.h
sc_command.h
sc_glyphs.h raw-коды CP866 для рамок и стрелок
sc_input.h
sc_screen.h
sc_panel.h
sc_store.h
sc_source.h
sc_job.h
sc_path.h
sc_platform.h
banks/
bank1_panel.c чтение/сортировка/обновление панелей
bank2_fileops.c копирование и диалоги операции
tests/
... target-тесты и данные для MAME
docs/
...
```
Граница файлов может уточняться по карте линковки. Нельзя объединять всё в
один большой модуль: SDCC/линкер должны иметь возможность не тянуть лишний
код и разнести холодные функции по банкам.
## 5. Состояние приложения
### 5.1. `ScApp`
В W2 хранится единственный объект состояния:
```c
typedef struct {
ScPanel panels[2];
uint8_t active_panel;
uint8_t running;
uint8_t video_mode_saved;
uint8_t original_video_mode;
uint8_t emm_block;
uint8_t panel_page[2];
uint8_t screen_page;
ScJob job;
int16_t last_error;
} ScApp;
```
Фактическое определение может отличаться, но следующие инварианты обязательны:
- `active_panel` равен 0 или 1;
- обе панели ссылаются на разные EMM-страницы;
- `job` либо неактивен, либо является единственным владельцем своих fd;
- номера блоков и страниц обнуляются сразу после освобождения;
- cleanup можно вызвать после любой частично завершённой стадии init.
### 5.2. `ScPanel`
```c
typedef struct {
char path[256];
uint16_t count;
uint16_t cursor;
uint16_t top;
uint32_t total_size;
uint8_t source_kind;
uint8_t phys_page;
uint8_t truncated;
uint8_t dirty;
} ScPanel;
```
Ограничения:
- `cursor < count`, если `count != 0`;
- `cursor == 0`, если `count == 0`;
- `top <= cursor`;
- видимый курсор лежит в диапазоне `top..top+26`;
- `count <= 640` для всей линейки 0.1–1.0;
- `path` всегда завершён нулём.
## 6. Хранилище каталогов
Публичный интерфейс не возвращает указатель в W3:
```c
int sc_store_get(const ScPanel *panel, uint16_t index, ScDirEntry *out);
int sc_store_put(ScPanel *panel, uint16_t index, const ScDirEntry *entry);
void sc_store_clear(ScPanel *panel);
```
Для пакетных операций допускается закрытая функция, которая отображает
страницу на короткое время. Она должна:
1. Сохранить текущую страницу W3.
2. Подключить страницу панели.
3. Выполнить только вычисления над памятью.
4. Не вызывать ESTEX, renderer, банковский dispatch или пользовательский
callback.
5. Восстановить W3 перед возвратом.
Сортировка PoC должна быть нерекурсивной и не использовать callback `qsort`,
чтобы избежать лишнего кода и банковских переходов. Предпочтительный первый
вариант — Shell sort над одной отображённой страницей.
## 7. Чтение настоящего каталога
Последовательность `sc_source_fs_scan()`:
1. Проверить и нормализовать путь.
2. Сохранить имя записи под курсором, если панель уже заполнена.
3. Очистить счётчики панели, но не освобождать её страницу.
4. При необходимости добавить синтетическую запись `..`.
5. Выполнить `ffirst("*.*")/fnext` с одним общим 256-байтовым near-буфером.
Шаблон `*` запрещён: P1 и P3 подтвердили, что DSS считает его именем без
расширения и не возвращает обычные `NAME.EXT`.
6. Пропустить `.` и `..` от DSS.
7. Преобразовать `ffblk_t` в `ScDirEntry` и последовательно записать в EMM.
8. При достижении 640 записей установить `truncated` и немедленно закончить
scan: следующий `F_FIRST` сам переинициализирует глобальный итератор DSS.
9. На стабильном DSS 1.71 код 35 означает, что закончилась единственная
16-КБ страница кэша каталога, а не весь каталог. Принять уже прочитанные
записи, установить `truncated` и добавить последней красную виртуальную
запись `>>> MORE...` с флагом `SC_ENTRY_DSS_LIMIT`. Она занимает один
слот, не является файлом и не участвует в файловых операциях.
10. Отсортировать записи: `..`, каталоги, файлы, маркер ограничения; внутри
обычных групп применить ключ и направление конкретной панели.
11. Найти прежнее имя и восстановить курсор.
12. Пометить панель и её экранные строки грязными.
Переборы двух панелей никогда не активны одновременно. `ffblk_t` не хранится
в EMM и не является частью состояния панели. Причина и варианты снятия
предела DSS разобраны в [dss-large-directories.md](dss-large-directories.md).
Флаг `SC_ENTRY_SELECTED` хранится в самой 24-байтовой EMM-записи и переезжает
вместе с ней при сортировке. Резидентная `ScPanel` содержит только счётчик и
32-битную сумму выбранных обычных файлов. `..` и виртуальные маркеры никогда
не выбираются; refresh в текущем срезе 0.2 явно сбрасывает выбор и статистику.
Одиночный `Insert` может менять флаг каталога, но все групповые операции
`+`, `-`, `*` сначала отбрасывают `FA_DIREC` и никогда не меняют ранее
установленный на каталоге флаг.
Маски `+`/`-` используют ASCII case-insensitive matcher с `*` и `?` только
для файлов; специальный DOS-шаблон `*.*` совпадает также с именами файлов
без точки. Редактор
маски является общей координатной модальной поверхностью в BANK2: он рисует
поле в экранной EMM-модели, поддерживает insertion, Backspace, Delete,
Left/Right/Home/End и не создаёт BIOS-окно.
F6/F7 используют тот же editor и один validator компонента DOS 8.3. Локальный
F6 не передаёт полные пути в DSS `RENAME`: отдельный банковый helper сохраняет
CWD в резидентный scratch, входит в каталог панели, проверяет отсутствие
target, вызывает `rename(old_basename, new_basename)` и восстанавливает CWD.
Перезапись существующей записи запрещена до системного вызова.
F8 без выбора удаляет один объект после подтверждения: `unlink` для файла и
`rmdir` для пустого каталога. Непустой каталог и виртуальная запись не
запускают разрушительную операцию. При наличии выбора F8 одним
подтверждением сначала удаляет выбранные файлы верхнего уровня, затем
обходит все выбранные directory roots общей BFS-очередью, удаляет найденные
файлы и выполняет `rmdir` каталогов обратным проходом. При ошибке или Esc
уже выполненные действия не откатываются; CWD и EMM-страница освобождаются.
Групповой F5 проходит EMM-store по индексам и передаёт `sc_copy_begin/step`
только записи с одновременно установленным `SC_ENTRY_SELECTED` и снятым
`FA_DIREC`. Между файлами долговременный указатель W3 не сохраняется. При
ошибке уже committed-файлы остаются, активный temp удаляется прежней job;
это первая зафиксированная политика частично успешной группы.
F5 одного текущего каталога использует `sc_tree`: на время операции он
выделяет отдельную EMM-страницу с BFS-очередью до 1008 каталогов. Узел хранит
только parent-index, компонент DOS 8.3 и depth; путь восстанавливается перед
действием. Первый 256-байтовый диапазон страницы хранит исходный CWD.
Глубина ограничена 32, а путь — 255 символами. Файлы по-прежнему проходят
через единственную 4-КБ copy-page. Existing target не merge-ится; target
внутри source запрещается до создания root. Групповой F5 добавляет все
выбранные каталоги верхнего уровня как независимые roots той же очереди
после завершения выбранных top-level файлов.
## 8. Renderer
### 8.1. Интерфейс
Минимальные операции:
```c
void sc_screen_clear(uint8_t attr);
void sc_screen_cell(uint8_t x, uint8_t y, uint8_t ch, uint8_t attr);
void sc_screen_text(uint8_t x, uint8_t y, const char *s, uint8_t attr,
uint8_t max_width);
void sc_screen_fill(uint8_t x, uint8_t y, uint8_t width,
uint8_t ch, uint8_t attr);
int sc_screen_scroll_rows(uint8_t x, uint8_t y, uint8_t width,
uint8_t height, int8_t delta);
void sc_screen_mark_rows(uint8_t first, uint8_t count);
int sc_screen_flush(void);
```
Экранный буфер — источник истины. Диалог рисуется в тот же буфер поверх
панелей. Для PoC допустима перерисовка панелей после закрытия диалога вместо
отдельного стека сохранённых окон.
### 8.2. Обновление
- Dirty mask занимает 32 бита в W2.
- Изменение курсора помечает старую и новую строку.
- Прокрутка панели помечает строки 1–27 её половины экрана.
- При изменении `top` ровно на одну строку `sc_screen_scroll_rows()` сдвигает
одновременно EMM-модель и экранный прямоугольник, затем renderer строит
только открытую строку. PgUp/PgDn и большой скачок делают полный redraw
области панели.
- Смена каталога перерисовывает соответствующую половину и общие строки
2931.
- Полный `sc_video_present_full()` используется после запуска, возврата из
`.EXE` и крупных изменений.
- `sc_video_present_rect()` группирует соседние грязные строки.
App-local WINREST wrapper проверен P1-пробой Commander: полный экран 80x32 и
окно 20x2 из offset `0x2000` восстановились корректно. P2.1 затем подтвердил
полную сверку через `RDCHAR`, оба направления прямоугольного scroll и 1000
операций. Координатный backend принят как основной пакетный способ вывода;
ограничения и артефакты описаны в
[результатах P2.1](p2-screen-probe-results.md).
## 9. Ввод и команды
`sc_input.c` преобразует `getkey()` и `kbd_mod_state()` в `ScKeyEvent`:
```c
typedef struct {
uint8_t ascii;
uint8_t scan;
uint8_t modifiers;
} ScKeyEvent;
```
Модификаторы хранят только Shift, Ctrl и Alt; состояния CapsLock, NumLock и
Rus/Lat не участвуют в выборе команды.
P1 установил особенность DSS: при модифицированной клавише бит 7 raw
scan-кода установлен. Например, Ctrl+R даёт `93`, Ctrl+PgUp — `D9`, Alt+F1
`BB`. `sc_input` нормализует их в `13`, `59`, `3B` через `scan & 0x7F`,
но только после чтения compact modifiers. Таблица команд никогда не содержит
raw-коды с битом 7.
`sc_command.c` превращает событие в `ScCommand`. Остальные модули не должны
сравнивать scan-коды непосредственно. Команды описаны в
[commander-keymap.md](commander-keymap.md).
В MAME `kbd_mod_state()` в момент возврата `getkey()` сохранил активный
модификатор: compact Ctrl=`02`, Alt=`04`, Shift=`01`. Для переносимости
управляющие ASCII-коды (`Ctrl+R` и подобные) всё равно имеют явные алиасы,
не зависящие от live-состояния.
## 10. Копирование как конечный автомат
Состояния PoC:
```text
IDLE
-> VALIDATE
-> CREATE_TEMP
-> OPEN_SOURCE
-> COPY_CHUNK <----+
-> CLOSE_FILES |
-> RENAME_TEMP |
-> REFRESH |
-> DONE |
|
COPY_CHUNK -----------+ пока done < total
|-> CANCEL -> CLEANUP -> DONE
+-> ERROR -> CLEANUP -> DONE
```
Правила:
- конечное имя проверяется до создания temp;
- temp создаётся в целевом каталоге с `O_CREAT|O_EXCL`;
- имя temp имеет форму `~SCxxxx.TMP` и перебирается с ограниченным числом
попыток;
- рабочая область находится в отдельной EMM-странице и временно отображается
в W3; 4-КБ массива в W2 и изменяемого буфера в банке кода нет;
- начальный логический размер блока — 4096 байт, чтобы между блоками часто
обновлять прогресс и проверять отмену; после измерений он может быть увеличен
вплоть до размера страницы;
- чтение выполняется существующим `bank_read_page()` прямо из открытого fd в
страницу; для записи нужен симметричный `bank_write_page()`, который сохраняет
и восстанавливает отображение W3;
- открыто не более двух fd;
- успешность `write()` определяется его библиотечным контрактом, а не
сырым DE ESTEX;
- при short write оставшаяся часть блока дописывается или операция завершается
ошибкой;
- после успешного закрытия temp конечное имя проверяется повторно, и только
затем temp переименовывается в него;
- FAT date/time задаются temp через `PUT_D_T` перед close; после rename
конечное имя получает исходные R/H/S/A через `ATTRIB`;
- ошибка после rename помечается как ошибка финализации уже committed-файла,
а не как полное отсутствие результата;
- любое завершение проходит через единый cleanup;
- после успеха перечитывается пассивная панель.
P19 добавляет policy существующего файла. Safe overwrite сначала переводит
старый target в уникальный `~SBxxxx.BAK`, затем commit-ит temp. При отказе
второго rename backup возвращается на место вместе с исходными атрибутами;
после успешных метаданных нового target backup удаляется. `overwrite all` и
`skip all` — локальное состояние одного F5. Readonly не наследует молча
`overwrite all`, а повторно показывает choice-dialog.
Для версии 0.2 этот автомат расширяется очередью файлов, обходом каталогов,
политиками overwrite/skip/rename и сохранением метаданных.
## 11. Запуск дочерней программы
Последовательность:
1. Завершить или запретить активный job.
2. Сформировать проверенный абсолютный путь `.EXE`.
3. Запомнить активную панель, курсоры и ожидаемый CWD; временно сделать путь
активной панели текущим каталогом child.
4. Выполнить синхронный ESTEX `EXEC` с `B=1` и сразу сохранить его результат
до других вызовов libc.
5. Восстановить ожидаемые диск и CWD.
6. Повторно установить текстовый режим 80x32 и палитру Commander, не меняя
сохранённую палитру для финального shutdown.
7. Перечитать обе панели: внешняя программа потенциально могла изменить оба
их каталога.
8. Полностью вывести экран из screen page.
9. При ошибке показать её штатной строкой состояния Commander.
P1 подтвердил контракт wrapper: C-сигнатура
`p1_exec(uint8_t path_mode, const char *path)` получает mode в A и path в DE,
затем перекладывает их в B и HL для ESTEX. `B=0` с коротким именем и `B=1`
с явным относительным путём работают; probe получил `0x5A`. В Commander
низкоуровневый `sc_platform_exec(path)` фиксирует `B=1`, принимает путь в HL
по `__sdcccall(1)` и возвращает код завершения как `int` в DE.
### 11.1. Командная строка и `Ctrl+O` в 1.0
В VC 4.05/4.99 `Ctrl+O` переключает панели и user screen, а редактор `DosBuf`
работает вместе с prompt и историей. Dos Navigator дополнительно показывает
самостоятельный `TCommandLine` с горизонтальной прокруткой и историей. На Sprinter
родительская DSS-shell приостановлена, поэтому её нельзя превратить в интерактивный
фон простым скрытием панелей.
Поэтому 1.0 использует собственный pseudo-shell:
- однострочный prompt на экране панелей и полноэкранный режим делят один
редактор, историю и модель текущего каталога;
- `Ctrl+O` переключает режимы; возврат к панелям полностью строит их из
`ScPanel`, поэтому отдельная копия экрана панелей не нужна;
- builtin-команды минимум `cd`, смена диска, `dir`, `cls`, `pwd`, `help`;
- внешняя команда запускается через подтверждённый ESTEX `EXEC`; после возврата
pseudo-shell восстанавливает видеорежим, CWD и свою экранную модель;
- вывод и скролл идут через тот же безоконный screen backend; прямой VRAM и BIOS
window manager не используются;
- полноэкранные ячейки могут повторно использовать текущую screen page. До
фиксации поведения внешнего `.EXE` отдельной пробой проверяется `WINCOPY`.
## 12. Ошибки и владение ресурсами
Низкий уровень возвращает `0/-1` и устанавливает `errno`. UI не анализирует
Carry Flag или регистры DSS.
Каждый ресурс имеет одного владельца:
| Ресурс | Владелец |
|---|---|
| общий EMM-блок PoC | `ScApp` |
| физическая страница панели | `ScPanel`, без права освобождения блока |
| экранная страница | `sc_screen`, без права освобождения блока |
| рабочая страница копирования | `ScApp`/copy job, без права освобождения блока |
| fd источника и temp | активный `ScCopyJob` |
| исходный видеорежим | `ScApp` |
| временно отображённая W3 | текущая функция storage/screen |
Cleanup выполняется в обратном порядке инициализации. Нельзя прятать
владение fd или EMM-блоком в локальном static-состоянии модуля.
## 13. Предварительная карта кода
HOME/W2 содержит только постоянно необходимое:
- crt0, банковские трамплины и используемую libc;
- init/cleanup;
- главный цикл;
- ввод и базовый dispatch;
- EMM accessors;
- screen flush;
- критическую обработку ошибок.
Предварительные банки:
| Банк | PoC | Полная версия |
|---:|---|---|
| 1 | panel scan, sort, redraw model | панели, сортировки, режимы панели |
| 2 | copy job, operation dialog | копирование/перемещение/удаление |
| 3 | не нужен | меню, конфигурация, история |
| 4 | не нужен | поиск и дерево |
| 5 | не нужен | архивные/виртуальные источники |
Номер банка является деталью сборки, а не публичным ABI. Банковские модули
не должны хранить долгоживущие payload-данные в локальном сегменте банка.
Краткоживущий `ffblk_t` является обычным near-буфером; закреплять его в хвосте
W1 бессмысленно, поскольку граница больших каталогов находится внутри DSS.
После каждой сборки проверяются:
- размер HOME вместе с DATA/BSS и heap;
- запас каждого банка до 16 КБ;
- отсутствие прямых вызовов в чужой банк;
- расположение трамплинов;
- стек W2 при всех вызовах ESTEX.
## 14. Эволюция после PoC
### Версия 0.2
- одностраничное хранилище остаётся ограничено 640 записями;
- выбранные файлы и маски;
- очередь операций;
- рекурсивный обход;
- local rename, mkdir, recursive group copy/delete выполнены; group move и
защитная policy остаются;
- транзакционная перезапись файлов и conflict-policy выполнены; directory
merge и delete/move policy остаются;
- атрибуты и время обычных файлов выполнены; метаданные каталогов — позже.
### Версия 0.4
- командная строка, история и полноэкранный pseudo-shell по `Ctrl+O`;
- associations/user menu;
- внешний viewer/editor/setup;
- конфигурационный файл;
- переключение дисков.
### Версия 0.6
- quick view и info source;
- дерево и поиск;
- фильтры и сравнение каталогов;
- подсчёт размеров каталогов;
- описания файлов.
### Версия 0.8+
- `SC_SOURCE_SEARCH` как виртуальная панель;
- `SC_SOURCE_ARCHIVE`;
- протокол внешних helper-программ;
- мышь, помощь, локализация;
- измеренная оптимизация кэшей и renderer.
### Версия 2.0+
- динамическое выделение EMM-страниц панели;
- предел 1024/2048 или отсутствие продуктового предела после измерений;
- межстраничная сортировка без полной копии каталога в W2;
- деградация при нехватке EMM и тесты частично выделенных цепочек страниц.
## 15. Архитектурные запреты
- Нельзя вызывать DSS при стеке вне W2.
- Нельзя держать одновременно восемь файловых дескрипторов.
- Нельзя сохранять W3 pointer в состоянии приложения.
- Нельзя менять W1 вручную из банковской функции.
- Нельзя вызывать чужой код при оставленной временной странице W3.
- Нельзя выполнять разрушительную операцию без известного cleanup-path.
- Нельзя смешивать вывод через `printf` с экранной моделью во время работы UI.
- Нельзя добавлять функцию DN только потому, что она присутствует в DN;
каждая функция должна соответствовать roadmap Commander.
@@ -0,0 +1,324 @@
# Sprinter Commander: клавиши и команды
Статус: PoC и первые команды 0.2 подтверждены в MAME
Связанные документы:
[требования](commander-requirements.md),
[архитектура](commander-architecture.md),
[план разработки](commander-roadmap.md)
## 1. Назначение
Документ отделяет физические клавиши Sprinter от логических команд
Commander. Только модуль `sc_input.c` знает scan-коды, а только
`sc_command.c` знает таблицу их назначения.
Панели и операции получают значения `ScCommand`, а не результаты `getkey()`.
Это позволяет позднее добавить мышь, переназначение клавиш и альтернативную
клавиатуру без изменений файловых функций.
## 2. Формат события
```c
enum {
SC_MOD_SHIFT = 0x01,
SC_MOD_CTRL = 0x02,
SC_MOD_ALT = 0x04
};
typedef struct {
uint8_t ascii;
uint8_t scan;
uint8_t modifiers;
} ScKeyEvent;
```
Правила нормализации:
- обычный символ записывается в `ascii`;
- расширенная клавиша записывается в `scan`, `ascii == 0`;
- при активном Ctrl/Alt/Shift DSS может установить бит 7 scan-кода;
диагностическая трассировка показывает raw-значение, а `sc_input`
передаёт командам базовый `scan & 0x7F`;
- левый и правый Shift объединяются в `SC_MOD_SHIFT`;
- левый и правый Ctrl объединяются в `SC_MOD_CTRL`;
- левый и правый Alt объединяются в `SC_MOD_ALT`;
- CapsLock, NumLock, ScrollLock, Insert и Rus/Lat не являются модификаторами
команды;
- управляющий ASCII-код имеет приоритет над live-состоянием модификатора;
- неизвестная комбинация преобразуется в `SC_CMD_NONE`.
## 3. Подтверждённые расширенные scan-коды
Значения уже объявлены в `<conio.h>`:
| Клавиша | Scan-код | Макрос |
|---|---:|---|
| F1 | `0x3B` | `KEY_F1` |
| F2 | `0x3C` | `KEY_F2` |
| F3 | `0x3D` | `KEY_F3` |
| F4 | `0x3E` | `KEY_F4` |
| F5 | `0x3F` | `KEY_F5` |
| F6 | `0x40` | `KEY_F6` |
| F7 | `0x41` | `KEY_F7` |
| F8 | `0x42` | `KEY_F8` |
| F9 | `0x43` | `KEY_F9` |
| F10 | `0x44` | `KEY_F10` |
| End | `0x51` | `KEY_END` |
| Down | `0x52` | `KEY_DOWN` |
| PgDn | `0x53` | `KEY_PGDN` |
| Left | `0x54` | `KEY_LEFT` |
| Delete | `0x55` | `KEY_DEL` |
| Right | `0x56` | `KEY_RIGHT` |
| Home | `0x57` | `KEY_HOME` |
| Up | `0x58` | `KEY_UP` |
| PgUp | `0x59` | `KEY_PGUP` |
| Insert | `0x50` | `KEY_INS` |
Все значения таблицы, включая `Insert` и `Delete`, подтверждены P1-пробой в
MAME. `F11` и `F12` в P1 не проверялись и остаются неподтверждёнными до
появления команды, которая их использует.
Фактическая последовательность MAME для навигации была:
`Up=58`, `Down=52`, `Left=54`, `Right=56`, `Home=57`, `End=51`,
`PgUp=59`, `PgDn=53`, `Insert=50`, `Delete=55`. F1–F10 дали непрерывный
диапазон `3B..44`.
## 4. Логические команды
Численные значения enum не являются стабильным ABI до версии 0.2.
```c
typedef enum {
SC_CMD_NONE = 0,
SC_CMD_CURSOR_UP,
SC_CMD_CURSOR_DOWN,
SC_CMD_CURSOR_LEFT,
SC_CMD_CURSOR_RIGHT,
SC_CMD_PAGE_UP,
SC_CMD_PAGE_DOWN,
SC_CMD_HOME,
SC_CMD_END,
SC_CMD_PANEL_SWITCH,
SC_CMD_OPEN,
SC_CMD_PARENT,
SC_CMD_ROOT,
SC_CMD_REFRESH,
SC_CMD_CANCEL,
SC_CMD_SELECT,
SC_CMD_SELECT_MASK,
SC_CMD_UNSELECT_MASK,
SC_CMD_INVERT_SELECTION,
SC_CMD_HELP,
SC_CMD_USER_MENU,
SC_CMD_VIEW,
SC_CMD_EDIT,
SC_CMD_COPY,
SC_CMD_MOVE_RENAME,
SC_CMD_MKDIR,
SC_CMD_DELETE,
SC_CMD_MAIN_MENU,
SC_CMD_QUIT,
SC_CMD_LEFT_DRIVE,
SC_CMD_RIGHT_DRIVE,
SC_CMD_SWAP_PANELS,
SC_CMD_TOGGLE_SHELL,
SC_CMD_QUICK_VIEW,
SC_CMD_INFO_PANEL,
SC_CMD_FIND,
SC_CMD_TREE
} ScCommand;
```
Дополнительные команды вводятся при появлении соответствующей функции, а не
заранее в обработчиках-заглушках.
## 5. Клавиши PoC 0.1
### 5.1. Навигация панели
| Клавиша | Команда | Поведение PoC |
|---|---|---|
| `Up` | `SC_CMD_CURSOR_UP` | На одну запись вверх |
| `Down` | `SC_CMD_CURSOR_DOWN` | На одну запись вниз |
| `PgUp` | `SC_CMD_PAGE_UP` | На 27 записей вверх |
| `PgDn` | `SC_CMD_PAGE_DOWN` | На 27 записей вниз |
| `Home` | `SC_CMD_HOME` | Первая запись |
| `End` | `SC_CMD_END` | Последняя запись |
| `Left` | `SC_CMD_NONE` | Не используется в одноколоночной панели PoC |
| `Right` | `SC_CMD_NONE` | Не используется в одноколоночной панели PoC |
| `Tab` | `SC_CMD_PANEL_SWITCH` | Переключить активную панель |
| `Enter` | `SC_CMD_OPEN` | Войти в каталог или запустить `.EXE` |
| `Ctrl+PgUp` | `SC_CMD_PARENT` | Перейти в родительский каталог |
| `Backspace` | `SC_CMD_PARENT` | То же, пока командной строки нет |
| `Ctrl+R` | `SC_CMD_REFRESH` | Перечитать активную панель |
| `Ctrl+F3` | `SC_CMD_SORT_NAME` | Имя; повторное нажатие меняет направление |
| `Ctrl+F4` | `SC_CMD_SORT_EXTENSION` | Расширение; повтор меняет направление |
| `Ctrl+F5` | `SC_CMD_SORT_DATE` | Дата/время; повтор меняет направление |
| `Ctrl+F6` | `SC_CMD_SORT_SIZE` | Размер; повтор меняет направление |
| `Insert` | `SC_CMD_SELECT` | Выбрать/снять текущую запись и перейти вниз |
| `*` | `SC_CMD_INVERT_SELECTION` | Инвертировать только файлы панели; каталоги не менятся |
`SC_CMD_CURSOR_LEFT/RIGHT` сохраняются в enum для будущего многоколоночного
режима, но PoC их не генерирует.
### 5.2. Функциональные клавиши PoC
| Клавиша | Команда | Состояние |
|---|---|---|
| `F1` | `SC_CMD_HELP` | Зарезервирована, неактивна |
| `F2` | `SC_CMD_USER_MENU` | Зарезервирована, неактивна |
| `F3` | `SC_CMD_VIEW` | Зарезервирована, неактивна |
| `F4` | `SC_CMD_EDIT` | Зарезервирована, неактивна |
| `F5` | `SC_CMD_COPY` | Один файл/каталог либо группа выбранных файлов и directory roots |
| `F6` | `SC_CMD_MOVE_RENAME` | Локальный rename одной записи; move/group позже в 0.2 |
| `F7` | `SC_CMD_MKDIR` | Создать каталог; 0.2, выполнено |
| `F8` | `SC_CMD_DELETE` | Одна запись либо выбранная группа; каталоги группы удаляются рекурсивно после одного подтверждения |
| `F9` | `SC_CMD_MAIN_MENU` | Зарезервирована, неактивна |
| `F10` | `SC_CMD_QUIT` | Открыть подтверждение штатного выхода |
PoC не должен выполнять разрушительное действие по неактивной клавише.
### 5.3. Общие клавиши PoC
| Клавиша | Команда | Контекст |
|---|---|---|
| `Esc` | `SC_CMD_CANCEL` | Закрыть сообщение или отменить job |
| `Esc` | `SC_CMD_NONE` | Обычный режим панелей |
| `Enter` | подтвердить | Диалог сообщения |
| `Y`/`N` | да/нет | Диалог подтверждения, если он появится |
## 6. Контекстная обработка
Одна клавиша может иметь разное действие в зависимости от режима. Приоритет
обработчиков фиксирован:
1. Активная аварийная ошибка.
2. Модальный диалог.
3. Активный job.
4. Командная строка или полноэкранный pseudo-shell версии 1.0.
5. Активная панель.
6. Глобальные команды приложения.
Примеры:
- Во время копирования `Esc` запрашивает отмену, но `F10` не завершает
процесс немедленно.
- В диалоге `Enter` подтверждает кнопку, а не открывает файл под курсором.
- После закрытия диалога то же событие не передаётся панели повторно.
- `F10` во время job игнорируется с подсказкой отменять операцию через `Esc`;
подтверждение выхода открывается только после завершения или отмены job.
- В режиме панелей печатный символ передаётся однострочному редактору;
`Enter` выполняет непустую строку, а при пустой строке открывает запись.
- В pseudo-shell `Up/Down` листают историю команд, а не панель; `Ctrl+O`
возвращает панели с сохранёнными путями и курсорами.
- `Esc` сначала очищает непустую команду, затем закрывает полноэкранный режим;
событие не передаётся следующему контексту повторно.
## 7. Нормализация управляющих ASCII-кодов
На части клавиатур `Ctrl+буква` может приходить как ASCII `0x010x1A` без
надёжного сохранения состояния Ctrl. Поэтому ввод должен распознавать
следующие значения независимо от `kbd_mod_state()`:
| ASCII | Комбинация | Команда |
|---:|---|---|
| `0x12` | `Ctrl+R` | `SC_CMD_REFRESH` |
| `0x15` | `Ctrl+U` | `SC_CMD_SWAP_PANELS`, после PoC |
| `0x0F` | `Ctrl+O` | `SC_CMD_TOGGLE_SHELL`, обязательно к 1.0 |
| `0x11` | `Ctrl+Q` | `SC_CMD_QUICK_VIEW`, после PoC |
| `0x0C` | `Ctrl+L` | `SC_CMD_INFO_PANEL`, после PoC |
`Tab` (`0x09`), `Enter` (`0x0D`), `Esc` (`0x1B`) и `Backspace` (`0x08`)
обрабатываются как обычные управляющие ASCII-клавиши.
P1 в MAME показал второй, фактически основной для текущего DSS путь:
| Комбинация | ASCII | Raw scan | Base scan | `kbd_mod_state()` | Compact |
|---|---:|---:|---:|---:|---:|
| `Ctrl+R` | `00` | `93` | `13` | `0028` | `02` |
| `Ctrl+PgUp` | `00` | `D9` | `59` | `0028` | `02` |
| `Alt+F1` | `00` | `BB` | `3B` | `0014` | `04` |
| `Esc` | `1B` | `01` | `01` | `0000` | `00` |
Таким образом, `sc_input` обязан поддерживать оба представления: сначала
управляющий ASCII-код, затем пару `(base scan, modifiers)`. Проверять raw
scan `93/D9/BB` прямо в таблице команд нельзя: базовый scan с модификатором
получается снятием бита 7. Скриншоты пробы находятся в
[`artifacts/mame-p1/keyboard-commander`](../artifacts/mame-p1/keyboard-commander/sprinter/0003.png).
## 8. Предварительная карта полной версии
Эта таблица резервирует привычные сочетания VC, но не является требованием
PoC.
| Клавиша | Команда | Планируемая версия |
|---|---|---:|
| `Insert` | Выделить/снять выделение и перейти вниз | 0.2, выполнено |
| `Gray +` или `+` | Выделить по маске | 0.2, обычный `+` выполнен |
| `Gray -` или `-` | Снять выделение по маске | 0.2, обычный `-` выполнен |
| `Gray *` или `*` | Инвертировать выделение | 0.2, `*` выполнено |
| `F1` | Помощь | 0.4 |
| `F2` | Пользовательское меню | 0.4 |
| `F3` | Просмотр | 0.4 |
| `F4` | Редактор | 0.4 |
| `F5` | Копирование | 0.1/0.2 |
| `F6` | Перемещение/переименование | 0.2, локальный rename выполнен |
| `F7` | Создать каталог | 0.2, выполнено |
| `F8` | Удалить | 0.2, single и recursive group выполнены |
| `F9` | Главное меню | 0.4 |
| `F10` | Выход | 0.1 |
| `Alt+F1` | Диск левой панели | 0.4 |
| `Alt+F2` | Диск правой панели | 0.4 |
| `Alt+F7` | Поиск файлов | 0.6 |
| `Alt+F10` | Дерево каталогов | 0.6 |
| `Ctrl+R` | Перечитать панель | 0.1 |
| `Ctrl+F3..F6` | Name/ext/date/size, повтор меняет направление | 0.2 |
| `Ctrl+U` | Обменять панели местами | 0.4 |
| `Ctrl+O` | Панели / интерактивный полноэкранный pseudo-shell | 0.4/1.0 |
| `Ctrl+PgUp` | Родительский каталог | 0.1 |
| `Ctrl+\` | Корень текущего диска | 0.2 |
| `Ctrl+Q` | Quick view | 0.6 |
| `Ctrl+L` | Информационная панель | 0.6 |
Shift-варианты функциональных клавиш не резервируются до появления
конкретной необходимости. Это предотвращает преждевременное копирование всей
таблицы VC без соответствующей функции.
## 9. Нижняя строка клавиш
Строка 31 содержит десять сегментов шириной по восемь символов. В PoC:
```text
1 Help 2 Menu 3 View 4 Edit 5 Copy 6 Ren 7 Mkdir 8 Delete9 Menu 10 Quit
```
Это логическая схема, а не точная строка байтов. Названия сокращаются до
доступной ширины. Поддерживаемые действия (`F5`, `F10`) рисуются активным
цветом, остальные — неактивным.
После добавления модификатора строка клавиш может временно показывать
назначения `Alt`/`Ctrl`, но это не входит в PoC.
## 10. Проверки клавиатуры P1/P2
В P1 на MAME подтверждены F1–F10, восемь навигационных клавиш,
Insert/Delete, Esc, Ctrl+R, Ctrl+PgUp и Alt+F1. Отдельная проба также
подтвердила compact-состояния Shift=`01`, Ctrl=`02`, Alt=`04`.
В P2 остаются поведенческие проверки уже внутри UI:
1. `Tab`, `Enter` и `Backspace` во всех контекстах.
2. Одновременное удержание нескольких модификаторов.
3. CapsLock, NumLock и Rus/Lat.
4. Отсутствие повторного выполнения события после закрытия диалога.
5. Keyboard repeat на границах списка.
6. Приём `Esc` во время копирования большого файла.
7. Повтор P1-набора на реальном Sprinter.
Любое расхождение с таблицей сначала оформляется отдельным репро и только
затем учитывается в Commander.
@@ -0,0 +1,427 @@
# Sprinter Commander: требования к PoC и границы проекта
Статус: требования PoC зафиксированы; gate P1 и ядро P2.1 пройдены в MAME
Целевая платформа: Sprinter Sp2000, Z80, DSS 1.71.57
Инструментарий: SDCC 4.5, `sprinter-cc`, MAME 0.287 / BIOS 3.06
Связанные документы:
[архитектура](commander-architecture.md),
[клавиши и команды](commander-keymap.md),
[план разработки](commander-roadmap.md),
[экранный backend P2](p2-screen-backend.md),
[результаты P2.1](p2-screen-probe-results.md),
[результаты skeleton P2](p2-skeleton-results.md),
[результаты файловых панелей P3](p3-panel-results.md)
## 1. Назначение документа
Документ фиксирует проверяемые требования к первой работающей версии
файлового менеджера для Sprinter. Рабочее имя проекта — **Sprinter
Commander**, рабочее имя исполняемого файла — `SPRCMD.EXE`.
PoC не является портом Volkov Commander. Это новая программа на C,
повторяющая привычную модель двухпанельного файлового менеджера с учётом
архитектуры памяти и API Sprinter.
## 2. Референсы и правила их использования
### 2.1. Volkov Commander 4.05
Используется как минимальный поведенческий образец:
- компоновка двух панелей;
- клавиатурная навигация;
- назначение функциональных клавиш;
- общий сценарий работы пользователя.
### 2.2. Volkov Commander 4.99
Используется как образец декомпозиции и источник функций для дальнейших
версий:
- отдельные подсистемы панелей, экрана, клавиатуры и диалогов;
- отдельные операции копирования, удаления и изменения атрибутов;
- просмотрщик, редактор, поиск и дерево каталогов;
- описания файлов и архивные панели;
- разделение резидентного ядра и загружаемых частей.
### 2.3. Dos Navigator 1.51
Используется только как архитектурный референс:
- единый цикл событий и команды приложения;
- отделение модели панели от её представления;
- абстракция источника файлов;
- длительные операции с прогрессом и отменой;
- виртуальные панели для архивов и результатов поиска;
- история, конфигурация и внешние инструменты.
Объектная система Turbo Vision, DOS overlays, XMS/EMS-код и прикладные
модули DN не переносятся.
Исходный код, строки интерфейса и ресурсы референсов не копируются без
отдельного решения по лицензированию. Реализация проекта должна оставаться
собственной реализацией по описанному поведению.
## 3. Определения версий
**PoC 0.1** доказывает техническую реализуемость панелей, EMM-хранилища,
экранного буфера, копирования и запуска программ.
**Рабочая версия** пригодна для повседневной работы с файлами и включает
безопасные групповые и рекурсивные операции.
**Полная версия 1.0** соответствует основным возможностям VC 4.99, но не
пытается повторить весь набор утилит Dos Navigator.
## 4. Пользовательский сценарий PoC
После запуска программа переключается в текстовый режим 80x32, выделяет
необходимую память и показывает две панели. Обе панели первоначально
открывают текущий каталог запуска.
Пользователь может:
1. Перемещать курсор по активной панели.
2. Переключать активную панель клавишей `Tab`.
3. Входить в каталог клавишей `Enter`.
4. Возвращаться в родительский каталог.
5. Перечитывать содержимое активной панели.
6. Копировать один обычный файл в каталог пассивной панели.
7. Запускать выбранный `.EXE` и возвращаться в Commander.
8. Выйти по `F10` с освобождением всех ресурсов.
## 5. Компоновка экрана PoC
Экран имеет 80 столбцов и 32 строки.
| Строки | Назначение |
|---:|---|
| 0 | Заголовки и текущие пути обеих панелей |
| 1–27 | Списки файлов, по 27 видимых записей |
| 28 | Итоги панелей: число записей и общий размер |
| 29 | Информация о текущей записи активной панели |
| 30 | Строка состояния, ошибок и прогресса |
| 31 | Подсказки функциональных клавиш |
Левая панель занимает столбцы 0–39, правая — 40–79. Точные символы рамок
и палитра не являются контрактом PoC, но все символы должны корректно
отображаться в CP866.
Renderer задаёт смысловые роли атрибутов независимо от конкретной палитры.
P2.2 подтвердил многоцветные планы в MAME: тема сохраняет исходные записи,
задаёт обе FLASH-пары и восстанавливает палитру при выходе. До аппаратного
подтверждения активная и пассивная панели всё равно различаются не только
цветом, но и глифами/маркерами.
Полная перерисовка выполняется из собственного экранного буфера через
`sc_video`. Commander не использует дескрипторы окон BIOS: панели, меню и
диалоги — логические прямоугольники приложения. `sc_video` выводит их в
координаты глобального экрана через ESTEX/BIOS-функции без идентификатора
окна; прямой доступ к VRAM не используется.
Изменение курсора или одной строки панели не должно требовать повторного
чтения каталога.
Прокрутка списка на одну строку может использовать прямоугольный ESTEX
`SCROLL 55h`, как `mdview2`, но рамка и соседняя панель остаются неподвижны,
а экранный EMM-буфер обновляется синхронно. Эта служба не является BIOS-
дескриптором окна; её аппаратная реализация исходниками `mdview2` не
доказана. Псевдографика задаётся raw-кодами CP866 из единого `sc_glyphs.h`.
## 6. Функциональные требования PoC
### REQ-BOOT: запуск и завершение
- **REQ-BOOT-01.** Программа должна явно установить текстовый режим 80x32.
- **REQ-BOOT-02.** Перед изменением видеорежима и текущего каталога должны
быть сохранены исходные значения, если DSS позволяет их получить.
- **REQ-BOOT-03.** При штатном выходе освобождаются все EMM-блоки и закрываются
все открытые файлы.
- **REQ-BOOT-04.** Ошибка инициализации не должна оставлять изменённый
видеорежим или выделенную память.
- **REQ-BOOT-05.** Если обязательные четыре EMM-страницы выделить нельзя,
программа выводит понятную ошибку и завершает работу.
- **REQ-BOOT-06.** `F10` в обычном режиме открывает подтверждение выхода;
положительный ответ запускает штатный cleanup, отрицательный возвращает к
панелям.
### REQ-PANEL: панели
- **REQ-PANEL-01.** Панели имеют независимые пути, курсор и позицию прокрутки.
- **REQ-PANEL-02.** Активная панель визуально отличается от пассивной.
- **REQ-PANEL-03.** Каталоги отображаются перед обычными файлами.
- **REQ-PANEL-04.** Внутри групп записи сортируются по имени без учёта
регистра ASCII.
- **REQ-PANEL-05.** Для записи отображаются имя, размер, дата, время и
атрибут каталога/файла в объёме, который помещается в ширину панели.
- **REQ-PANEL-06.** После перечитывания каталог по возможности сохраняет
курсор на прежнем имени; если оно исчезло — на ближайшей допустимой
позиции.
- **REQ-PANEL-07.** Полный scan DSS использует `*.*`; шаблон `*` не считается
эквивалентным, поскольку не возвращает обычные имена с расширением.
- **REQ-PANEL-08.** Пустой каталог отображается корректно и остаётся
управляемым.
- **REQ-PANEL-09.** Родительский каталог представлен синтетической записью
`..`, кроме корня диска.
- **REQ-PANEL-10.** Записи `.` и `..`, возвращённые DSS, не должны
дублировать синтетическую запись.
- **REQ-PANEL-11.** Во всей линейке 0.1–1.0 панель хранит не более
640 записей, включая синтетическую `..`. При превышении лимита
показывается предупреждение; запись за границу EMM-страницы
запрещена. Многостраничные панели и предел более 640 записей
относятся только к версии 2.0+.
- **REQ-PANEL-12.** Если DSS завершил `F_FIRST/F_NEXT` кодом 35
(`TOO_MANY_FILES_IN_DIR`), уже прочитанные записи остаются доступны, а
последней добавляется красная виртуальная запись `>>> MORE...`. Она
сообщает о недоступном остатке каталога, не сортируется вместе с файлами
и не может быть целью файловой операции.
### REQ-NAV: навигация
- **REQ-NAV-01.** Поддерживаются `Up`, `Down`, `PgUp`, `PgDn`, `Home`, `End`.
- **REQ-NAV-02.** `Tab` переключает активную панель без изменения каталогов.
- **REQ-NAV-03.** `Enter` на каталоге открывает его в активной панели.
- **REQ-NAV-04.** `Ctrl+PgUp` и `Backspace` открывают родительский каталог.
- **REQ-NAV-05.** `Ctrl+R` перечитывает активную панель.
- **REQ-NAV-06.** Навигация внутри уже прочитанной панели не обращается к
файловой системе.
- **REQ-NAV-07.** Операция с активной панелью не должна неожиданно менять
путь пассивной панели.
### REQ-FS: работа с DSS и путями
- **REQ-FS-01.** PoC работает с именами DOS 8.3. Длинные имена не входят в
требования до отдельного подтверждения поддержки DSS.
- **REQ-FS-02.** Максимальный внутренний путь — 255 байт плюс завершающий
ноль.
- **REQ-FS-03.** Склейка пути и имени проверяет переполнение до обращения к
DSS.
- **REQ-FS-04.** Контекст текущего каталога DSS считается глобальным
ресурсом. После временной смены каталога модуль обязан восстановить
ожидаемый каталог приложения.
- **REQ-FS-05.** Одновременно приложение держит не более трёх файловых
дескрипторов; штатная операция копирования использует два.
- **REQ-FS-06.** Конец перебора каталога отличается от настоящей ошибки
`fnext()` по `errno` и контексту операции.
### REQ-COPY: безопасное копирование одного файла
- **REQ-COPY-01.** `F5` копирует выбранный обычный файл в текущий каталог
пассивной панели.
- **REQ-COPY-02.** Копирование каталогов и групп файлов в PoC запрещено с
явным сообщением.
- **REQ-COPY-03.** Если конечное имя уже существует, PoC отказывается от
операции и не изменяет существующий файл.
- **REQ-COPY-04.** Данные копируются блоками, а общий размер и позиция имеют
32-битный тип.
- **REQ-COPY-05.** Поддерживаются файлы больше 64 КБ.
- **REQ-COPY-06.** Целевой файл сначала создаётся под уникальным временным
именем через семантику `O_EXCL`, после успешного закрытия переименовывается
в конечное имя.
- **REQ-COPY-07.** При ошибке или отмене временный файл закрывается и
удаляется.
- **REQ-COPY-08.** Между блоками проверяется `Esc`, а строка состояния
обновляет прогресс.
- **REQ-COPY-09.** Нулевой файл копируется корректно.
- **REQ-COPY-10.** В PoC сохранение времени и атрибутов файла не обязательно,
но должно быть предусмотрено в интерфейсе операции.
- **REQ-COPY-11.** Непосредственно перед переименованием временного файла
конечное имя проверяется повторно. Если оно появилось, операция завершается
без его изменения.
- **REQ-COPY-12.** Данные между открытыми файлами проходят непосредственно
через выделенную EMM-страницу, отображаемую в W3. Полноразмерный блоковый
буфер в W2 и изменяемый буфер внутри банка кода запрещены.
- **REQ-COPY-13.** Начиная с P18 успешно скопированный обычный файл получает
исходные FAT date/time и атрибуты readonly/hidden/system/archive. Время
задаётся temp до `close`, атрибуты — конечному имени после commit.
- **REQ-COPY-14.** Начиная с P19 существующий файл обрабатывается policy
overwrite/skip/rename; варианты overwrite-all и skip-all ограничены одним
F5. Overwrite использует backup в том же каталоге и rollback при отказе
commit, а readonly требует явного повторного решения.
### REQ-EXEC: запуск программ
- **REQ-EXEC-01.** `Enter` на имени с расширением `.EXE` запускает программу
через ESTEX `EXEC`.
- **REQ-EXEC-02.** Перед запуском Commander сохраняет логическое состояние
панелей и закрывает временные ресурсы операции.
- **REQ-EXEC-03.** После возврата восстанавливаются видеорежим, экран и
ожидаемый текущий каталог.
- **REQ-EXEC-04.** Активная панель перечитывается после возврата; пассивная
перечитывается, если запущенная программа могла изменить её каталог.
- **REQ-EXEC-05.** Ошибка загрузки `.EXE` показывается без завершения
Commander.
- **REQ-EXEC-06.** Обычный файл неизвестного типа в PoC не запускается.
### REQ-UI: сообщения и состояние
- **REQ-UI-01.** Ошибки не выводятся поверх структуры панели случайными
вызовами `printf`; они проходят через единый модуль сообщений.
- **REQ-UI-02.** Для каждой ошибки показываются действие, имя объекта и
текст/код ошибки, если они доступны.
- **REQ-UI-03.** `Esc` закрывает сообщение или отменяет текущую длительную
операцию; в обычном состоянии он не завершает программу.
- **REQ-UI-04.** Неподдерживаемые в PoC функциональные клавиши либо не
показаны, либо показаны неактивным цветом.
- **REQ-UI-05.** F6 local rename и F7 mkdir принимают только один проверенный
компонент DOS 8.3; `..`, маркер DSS и неоднозначная группа не передаются
файловой системе.
- **REQ-UI-06.** Rename обязан проверить отсутствие нового имени до DSS
`RENAME`, сохранить CWD и восстановить его после успеха или ошибки.
- **REQ-UI-05.** После закрытия сообщения восстанавливается экран из
логического экранного буфера.
### REQ-MEM: память
- **REQ-MEM-01.** Целевая конфигурация PoC — `--memory big --safe`.
- **REQ-MEM-02.** W2 содержит резидентное ядро, обычные данные, heap и стек;
банковские функции размещаются в W1; W3 используется только для EMM-
данных.
- **REQ-MEM-03.** PoC выделяет четыре 16-КБ страницы: две для панелей, одну
для экранного буфера и одну для рабочей области копирования.
- **REQ-MEM-04.** Прикладной код не хранит указатели в диапазон
`0xC0000xFFFF` между вызовами хранилища.
- **REQ-MEM-05.** Любой модуль, временно меняющий W3, восстанавливает прежнюю
страницу до возврата и до вызова чужого кода.
- **REQ-MEM-06.** Повторное чтение каталогов не выделяет новые блоки.
- **REQ-MEM-07.** Освобождение частично созданного приложения идемпотентно на
уровне состояния приложения: каждый реально выделенный блок освобождается
ровно один раз.
- **REQ-MEM-08.** Рабочая EMM-страница копирования принадлежит общему блоку
`ScApp`; copy job не выделяет и не освобождает её на каждый файл.
## 7. Формат записи каталога PoC
Логический формат записи фиксирован как 24 байта:
```c
typedef struct {
char name[13];
uint32_t size;
uint16_t date;
uint16_t time;
uint8_t attr;
uint8_t flags;
uint8_t reserved;
} ScDirEntry;
```
На этапе реализации обязательна compile-time или build-time проверка
`sizeof(ScDirEntry) == 24`.
Флаги PoC:
- `SC_ENTRY_PARENT` — синтетическая запись `..`;
- `SC_ENTRY_SELECTED` — выбор обычной записи в версии 0.2;
- `SC_ENTRY_DSS_LIMIT` — виртуальная запись о недоступном остатке каталога;
- остальные биты должны быть нулевыми.
В одной странице используется не более 640 записей; последние 1024 байта
остаются резервом и не входят в массив записей. Служебное
состояние панели хранится в W2, а не в начале EMM-страницы.
## 8. Нефункциональные требования
- **REQ-NF-01.** Исходный код и комментарии проекта ведутся на русском.
- **REQ-NF-02.** Сборка выполняется штатным `sprinter-cc` без отдельной
проприетарной цепочки.
- **REQ-NF-03.** После каждого этапа сохраняется отчёт размеров HOME и
банков; переполнение банка является ошибкой сборки.
- **REQ-NF-04.** Реакция на навигационную клавишу в уже прочитанной панели не
должна визуально зависать; целевой ориентир в MAME — менее 100 мс.
- **REQ-NF-05.** Операция дольше 500 мс должна показывать состояние или
прогресс.
- **REQ-NF-06.** Все воспроизводимые дефекты DSS или SDCC подтверждаются
отдельным тестом, `.asm`, дампом или другим артефактом.
- **REQ-NF-07.** Рабочая версия должна проверяться в MAME и на реальном
Sprinter; PoC может быть принят по MAME с обязательным последующим
hardware smoke-test.
## 9. Что не входит в PoC 0.1
- удаление файлов;
- перезапись существующих файлов;
- перемещение и переименование пользователем;
- создание каталогов;
- рекурсивные операции;
- групповое выделение и маски;
- командная строка и история;
- встроенные viewer/editor;
- дерево и поиск;
- архивы и виртуальные панели;
- мышь;
- конфигурационный файл, темы и локализация;
- длинные имена;
- фоновые операции и многозадачность.
## 10. Критерии приёмки PoC
PoC считается завершённым, если одновременно выполнены условия:
1. Программа стабильно запускается в MAME с двухпанельным экраном.
2. Обе панели независимо проходят дерево каталогов и корректно работают с
пустым каталогом.
3. Хранилище панели проверено синтетическим source на границах 640/641 без
выхода за EMM-страницу; на DSS 1.71 превышение его границы 512 физических
FAT-записей даёт управляемый частичный список с красным маркером.
Оба случая подтверждены MAME-пробами в
[результатах P3](p3-panel-results.md).
4. Сто последовательных перечитываний и переходов не меняют число свободных
EMM-страниц.
5. Копируются и побайтно совпадают файлы размеров 0, 1, 4095, 4096, 65535,
65536 байт и более 1 МБ.
6. Отмена и ошибка записи не оставляют временный файл.
7. Существующий целевой файл никогда не меняется.
8. Тестовый `.EXE` запускается, завершается, после чего Commander полностью
восстанавливает экран и продолжает работу.
9. Все пути ошибок возвращают приложение в управляемое состояние.
10. На выходе восстановлен видеорежим, закрыты дескрипторы и освобождены
выделенные EMM-блоки.
## 11. Граница полной версии 1.0
Версия 1.0 должна дополнительно включать:
- групповые и рекурсивные копирование, перемещение и удаление;
- безопасную политику перезаписи;
- сохранение атрибутов и времени;
- независимую для каждой панели сортировку по имени, расширению,
размеру и дате файла в обоих направлениях;
- переключение дисков;
- командную строку, историю, интерактивный полноэкранный
режим псевдооболочки по `Ctrl+O` и ассоциации;
- настраиваемую подсветку имён по расширению;
- просмотрщик и редактор, допустимо отдельными `.EXE`;
- меню, диалоги, помощь и конфигурацию;
- быстрый поиск, дерево и поиск файлов;
- информационную и quick-view панели;
- базовую работу с архивами;
- мышь.
Обязательные детали этих функций:
- групповые `+`, `-` и `*` работают только над файлами и не меняют флаг
каталога, даже если он был установлен отдельным `Insert`;
- `Ctrl+O` не просто скрывает панели, а переключает между ними и
управляемым полноэкранным режимом командной строки;
- полноэкранный режим и однострочный prompt используют один редактор строки и
общую историю; внешние `.EXE` запускаются через ESTEX `EXEC`;
- синтетическая `..` всегда первая, каталоги остаются перед файлами,
а направление применяется внутри этих групп;
- пункт сортировки «дата» использует метку создания/модификации, которую
реально возвращает файловая система; DSS `F_FIRST/F_NEXT` сейчас даёт
метку last-write, поэтому отдельный выбор creation не имитируется
дубликатом даты и требует отдельной платформенной пробы;
- при отсутствии файла цветов каталоги и `..` имеют белый цвет, `.EXE`
жёлтый, остальные файлы — светло-серый;
- предел 640 записей не снимается ни одной из этих функций.
Многостраничное/динамическое хранилище панели, межстраничная сортировка и
показ 1024/2048 или неограниченного числа файлов не входят в 1.0 и
резервируются для 2.0+.
Терминал, модем, CD-плеер, форматирование, восстановление дисков, электронная
таблица, игры и многооконный desktop Dos Navigator не входят в определение
Commander 1.0.
@@ -0,0 +1,751 @@
# Sprinter Commander: план разработки
Статус: рабочий план
Связанные документы:
[требования](commander-requirements.md),
[архитектура](commander-architecture.md),
[клавиши и команды](commander-keymap.md),
[платформенные пробы P1](p1-platform-probes.md),
[экранный backend P2](p2-screen-backend.md),
[результаты P2.1](p2-screen-probe-results.md),
[результаты P2.2](p2-palette-results.md),
[результаты skeleton P2](p2-skeleton-results.md),
[результаты файловых панелей P3](p3-panel-results.md),
[результаты копирования P4.1](p4-copy-results.md),
[результаты запуска EXE P4.2](p4-exec-results.md),
[результаты P5](p5-stability-results.md),
[результаты F7/P9](p9-mkdir-results.md),
[результаты F6/P10](p10-rename-results.md),
[результаты F8/P11](p11-delete-results.md),
[результаты group-copy/P12](p12-group-copy-results.md),
[результаты group-delete/P13](p13-group-delete-results.md),
[результаты стандартных цветов/P14](p14-default-colors-results.md),
[результаты recursive copy/P15](p15-tree-copy-results.md),
[результаты recursive group-copy/P16](p16-group-tree-copy-results.md),
[результаты recursive group-delete/P17](p17-tree-delete-results.md),
[результаты сохранения метаданных/P18](p18-metadata-results.md),
[результаты conflict-policy/P19](p19-copy-policy-results.md),
[UI-ориентир VC](ui-reference.md)
Текущее состояние:
- P0 — завершена первая редакция документации;
- P1 — обязательные платформенные примитивы подтверждены в MAME 0.287;
проверка на реальном Sprinter ожидается;
- P2.1 — ядро координатного экранного backend прошло MAME 0.287;
- P2 — многоцветный двухпанельный skeleton прошёл полный MAME-сценарий,
включая scroll, диалоги и повторный cleanup; аппаратная проверка ожидается;
- P2.2 — системная многоцветная палитра прошла отдельный MAME-тест;
аппаратная проверка ожидается;
- P3 — реальные две панели, EMM-store с границей 640/641, сортировка,
навигация, синтетическая `..` и предел DSS 35 прошли диагностические и
интеграционные MAME-тесты; аппаратная проверка ожидается.
- P4 — безопасное однофайловое копирование через EMM/W3 и запуск `.EXE` с
полным восстановлением окружения прошли HDD-сценарии MAME; аппаратная
проверка ожидается.
- P5 — завершён в MAME: пройдены 20 последовательных copy со всеми граничными
размерами, 100 refresh, три lifecycle, ENOSPC, readonly, ошибки
read/rename/ENOENT, отмена перед commit, смена носителя, отсутствующий EXE и
реальный `mdview2`. Аппаратный smoke-тест остаётся внешней проверкой.
- 0.2 — начат: независимые сортировки name/ext/size/date и reverse, выбор
`Insert`, `*`, `+`/`-` с масками, F7 mkdir и локальный F6 rename
реализованы и прошли target/UI-сценарии MAME; следующим идёт расширение
файловой job и рекурсивная обработка каталогов. Групповые F5 и F8 уже
обрабатывают файлы и выбранные directory roots; для F8 каталоги удаляются
после файлов обратным проходом очереди. F5 сохраняет FAT date/time и
файловые атрибуты R/H/S/A. Conflict-policy overwrite/skip/rename и all,
readonly re-prompt, backup/rollback выполнены; P5/P18 повторно пройдены.
Также досрочно реализован стандартный fallback цветов имён без файла
конфигурации.
## 1. Целевые результаты
Проект делится на три продуктовых рубежа:
1. **PoC 0.1** — доказана работа архитектуры на Sprinter.
2. **Рабочая версия 0.4** — Commander пригоден для повседневных файловых
операций.
3. **Полная версия 1.0** — реализован основной набор возможностей VC 4.99 с
адаптацией к Sprinter.
Dos Navigator задаёт идеи для расширяемости, но не объём версии 1.0.
Вся линейка до 1.0 включительно использует одну EMM-страницу на панель и
жёсткий предел 640 показываемых записей. Его снятие — отдельная архитектурная
задача 2.0+.
## 2. Допущения для оценки
Оценки приведены для одного разработчика, знакомого с текущим Sprinter
C-Compiler и имеющего доступ к MAME. Один рабочий день означает день
сосредоточенной разработки, а не календарный день.
В оценку входят:
- реализация;
- целевые тесты;
- анализ `.asm`/`.map` при платформенной проблеме;
- MAME-прогон;
- обновление документации.
В оценку не входят:
- исправление неизвестных дефектов DSS;
- разработка отсутствующего архиватора;
- длительное ожидание доступа к реальному железу;
- перенос стороннего editor/viewer;
- создание графического режима интерфейса.
Ожидаемая точность оценки:
- PoC: `-20% / +40%`;
- этапы после PoC: `-25% / +60%`;
- архивы и editor: оцениваются повторно после исследования доступных программ.
## 3. Сводный план
| Этап | Результат | Оценка |
|---|---|---:|
| P0 | Спецификация и архитектура | 2–3 дня |
| P1 | Проверенные платформенные примитивы | 3–5 дней |
| P2 | Запускаемый двухпанельный skeleton | 45 дней |
| P3 | Полноценная навигация по двум панелям | 5–7 дней |
| P4 | Копирование одного файла и EXEC | 5–7 дней |
| P5 | Стабилизация PoC | 3–5 дней |
| 0.2 | Безопасное файловое ядро | 5–7 недель |
| 0.4 | Рабочая оболочка и внешние инструменты | 4–6 недель |
| 0.6 | Поиск, дерево и расширенные панели | 5–7 недель |
| 0.8 | Виртуальные источники и архивы | 6–10 недель |
| 1.0 | Надёжность, производительность, релиз | 5–8 недель |
PoC требует ориентировочно 22–32 рабочих дня. Полная версия 1.0 — примерно
29–44 рабочих недели с учётом PoC. После исследования P1 оценки должны быть
пересмотрены по фактической скорости разработки.
## 4. Зависимости этапов
```text
P0 documentation
|
v
P1 platform probes
|
+------> EXEC probe -------------------+
| |
v v
P2 app/screen/input skeleton P4 EXEC
| ^
v |
P3 panel/store/filesystem --------> P4 copy
| |
+------------------+-------------------+
v
P5 PoC hardening
|
v
0.2 safe file core
|
+----------+-----------+
v v
0.4 shell/helpers 0.6 search/tree
+----------+-----------+
v
0.8 virtual sources
|
v
1.0
|
v
2.0+ dynamic panel store
```
0.6 допускается начинать после стабилизации интерфейса панелей 0.2, но релиз
0.8 требует законченных 0.4 и 0.6.
## 5. Фаза P0 — проектирование
### Цель
Превратить общую идею Commander в набор контрактов, по которым можно писать
и проверять код без постоянного возврата к исходникам VC.
### Работы
- зафиксировать границы PoC и версии 1.0;
- определить компоновку экрана;
- определить `ScDirEntry`, `ScPanel`, события и команды;
- выбрать режим памяти и владение EMM;
- описать безопасное копирование;
- описать запуск дочерней программы;
- определить клавиши PoC и будущий keymap;
- составить acceptance tests;
- зафиксировать лицензионное правило для референсов.
### Артефакты
- `docs/commander-requirements.md`;
- `docs/commander-architecture.md`;
- `docs/commander-keymap.md`;
- `docs/commander-roadmap.md`.
### Критерий выхода
Нет неизвестного продуктового решения, мешающего начать платформенные тесты.
Непроверенные технические детали перечислены как задачи P1, а не скрыты в
предположениях архитектуры.
## 6. Фаза P1 — платформенные пробы
### Цель
Получить минимальные воспроизводимые программы для всех примитивов, от
которых зависит PoC.
### P1.1. Каталоги и пути
Проверить:
- `getcwd`, абсолютные и относительные пути;
- `chdir` между двумя каталогами и дисками;
- шаблон для перечисления всех файлов;
- возвращает ли DSS `.` и `..`;
- конец `fnext()` и значение `errno`;
- имя корня и переход к родителю;
- максимальную практическую длину пути;
- поведение при смене/отсутствии носителя.
Результат: таблица фактического поведения и тестовый `.exe`.
### P1.2. EMM
Проверить:
- выделение блока из трёх страниц;
- получение всех физических страниц;
- чтение/запись начала и конца каждой страницы;
- сохранение W3 вокруг `bank_read/bank_write`;
- повторное выделение/освобождение;
- понятную ошибку при нехватке памяти.
Результат: подтверждённая схема трёх страниц PoC.
### P1.3. Экран
На базе существующего теста `winrest` проверить app-local wrapper:
- полный экран 80x32;
- вывод с ненулевого offset;
- частичный диапазон строк;
- все используемые атрибуты;
- восстановление после смены видеорежима;
- сохранность IX и W3.
Результат: утверждённый ABI `sc_platform_winrest()`.
### P1.4. Клавиатура
Записать фактические `ascii`, `scan`, `kbd_mod_state` для клавиш из
`commander-keymap.md`. Отдельно проверить быстрое отпускание Ctrl после
`Ctrl+PgUp` и `Ctrl+R`.
Результат: тестовая таблица и список подтверждённых комбинаций.
### P1.5. EXEC
Проверить:
- формат параметров ESTEX `EXEC`;
- требуется ли отдельный `WAIT`;
- возврат к вызывающей программе;
- состояние W1/W2/W3 после возврата;
- состояние видеорежима и CWD;
- коды ошибок отсутствующего и повреждённого `.EXE`;
- взаимодействие с открытым загрузочным fd multi-bank программы.
Результат: app-local wrapper и отдельный repro.
### P1.6. Информация о диске и метаданные
`DSKINFO` подтверждён в P1. `ATTRIB`, получение и установка даты/времени не
блокируют базовый PoC и проверяются перед реализацией соответствующих
операций версии 0.2.
### Критерий выхода P1
- Все обязательные wrappers имеют воспроизводимый тест.
- Необъяснённых регистровых соглашений нет.
- Известно минимальное число требуемых страниц и fd.
- Архитектура обновлена по результатам проб.
Критерий достигнут для MAME 0.287 / BIOS 3.06. Подтверждены EMM, DSKINFO,
F_FIRST/F_NEXT, полный и частичный WINREST, клавиши PoC, EXEC обоих режимов,
WAIT=`0x5A`, восстановление W1/W2/W3 и повторный EMM-тест после child.
Фактические значения и скриншоты приведены в
[p1-platform-probes.md](p1-platform-probes.md).
Расширенные отрицательные случаи не блокируют skeleton P2 и перенесены к
этапам, где появится использующий их код:
- переходы между дисками, корень, длинный путь и смена носителя — P3/P5;
- исчерпание EMM — P5;
- отсутствующий/повреждённый EXE и multi-bank loader fd — P4/P5;
- ATTRIB и изменение даты/времени — версия 0.2.
## 7. Фаза P2 — skeleton приложения
### Цель
Получить запускаемое приложение с двумя пустыми панелями, экранной моделью и
управлением, но без чтения каталога.
### Работы
1. Создать `Makefile`, `src/`, `include/`, `banks/`, `tests/`.
2. Добавить `ScApp` и init/cleanup.
3. Настроить сборку `--memory big --safe`.
4. Выделить EMM-блок и получить три страницы.
5. Зафиксировать прошедший P2.1 coordinate-screen probe: полный и частичный
`WINREST`, `RDCHAR`, оба направления прямоугольного `SCROLL`, 1000
операций и сохранение W3.
6. Реализовать `sc_video_system` на `WINREST`/`WRCHAR`/`SCROLL`; не
использовать BIOS window descriptors и прямой доступ к VRAM.
7. Реализовать screen buffer и полный/частичный present через `sc_video`.
8. Добавить `sc_glyphs.h` с CP866-глифами и test рамок, стрелок и block-
элементов.
9. Реализовать прямоугольный scroll одной панели в обоих направлениях;
синхронно сдвигать экранную EMM-модель и проверять границы прямоугольника.
10. Нарисовать две панели и нижнюю строку клавиш.
11. Реализовать `ScKeyEvent`, `ScCommand` и главный цикл.
12. Реализовать `Tab`, навигационные команды-заглушки и подтверждение `F10`.
13. Добавить единый message dialog.
14. Настроить первый интерактивный MAME-тест со скриншотом.
15. Подключить прошедший P2.2 модуль темы: сохранить затрагиваемые записи
четырёх планов, задать одинаковые normal/FLASH-пары и восстановить их при
cleanup. Повторить тот же тест на реальном Sprinter.
### Критерий выхода P2
- `SPRCMD.EXE` собирается в `big`.
- Экран соответствует схеме 80x32.
- `Tab` меняет активную рамку.
- Однострочный scroll не двигает рамку, соседнюю панель и общие строки.
- Все соединения рамок, стрелки и block-элементы отображаются ожидаемыми
CP866-глифами.
- Экран остаётся читаемым во времени с подключённым многоцветным профилем.
- Диалог F10 работает в обоих направлениях.
- Выход освобождает EMM; повторные запуски не уменьшают свободную память.
- В `.map` есть зафиксированный запас HOME и банков.
MAME-часть этих критериев достигнута 6 сентября 2026 года. Текущий skeleton
имеет 7 276 байт heap в HOME, пока не использует W1-банки и дважды подряд
восстановил число свободных EMM-страниц после выхода. Отдельный `sc_theme`
сохранил/установил/восстановил четыре палитровых плана. Подробный сценарий и
кадры: [p2-skeleton-results.md](p2-skeleton-results.md).
Аппаратный прогон остаётся внешним gate. Многоцветная тема P2.2 прошла MAME;
на реальном Sprinter она проверяется вместе с остальными критериями P2.
## 8. Фаза P3 — панели и каталог
### Цель
Реализовать независимую навигацию по настоящей файловой системе.
### Работы
- `ScDirEntry` и build-time проверка размера 24 байта;
- EMM store get/put/clear;
- scan настоящего каталога;
- синтетический `..`;
- сортировка каталогов и файлов;
- panel renderer;
- cursor/top и постраничная навигация;
- вход в каталог, parent, refresh;
- сохранение позиции по имени;
- отдельные пути панелей;
- предупреждение о пределе 640 записей и отдельный красный маркер
ограничения DSS после ошибки 35;
- отображение ошибок носителя.
### Порядок реализации
1. Одна панель, маленький каталог.
2. Пустой каталог и корень.
3. Сортировка.
4. Прокрутка за 27 строк.
5. Вторая независимая панель.
6. Большой каталог и предел страницы.
7. Ошибки и восстановление CWD.
### Критерий выхода P3
- Выполнены `REQ-PANEL`, `REQ-NAV` и `REQ-FS` PoC.
- Сто переходов/refresh не меняют EMM free pages.
- Каталог на пределе не повреждает screen page.
- Перемещение курсора не вызывает `ffirst/fnext`.
- После ошибки обе панели остаются управляемыми.
## 9. Фаза P4 — копирование и запуск
### P4.1. Копирование
Порядок работ:
1. Проверка выбранной записи и построение путей.
2. Проверка отсутствия конечного имени.
3. Генерация temp с `O_EXCL`.
4. Зарезервировать четвёртую EMM-страницу как рабочую область copy job.
5. Добавить симметричные операции `bank_read_page()`/`bank_write_page()` для
файлового обмена через W3 без полноразмерного буфера в W2.
6. Копирование логическими блоками 4096 байт; возможное увеличение блока —
только после измерения отзывчивости и скорости.
5. 32-битный прогресс.
6. Отмена через `Esc`.
7. Cleanup на каждой точке ошибки.
8. Повторная проверка конечного имени.
9. Rename temp и refresh пассивной панели.
10. Тесты размеров и побайтовое сравнение.
### P4.2. EXEC
Порядок работ:
1. Запуск минимального дочернего `.EXE`.
2. Восстановление видеорежима и экрана.
3. Восстановление CWD.
4. Refresh панелей.
5. Обработка ошибки запуска.
6. Запуск существующего viewer как интеграционный тест.
### Критерий выхода P4
- Выполнены `REQ-COPY` и `REQ-EXEC`.
- Большой файл копируется без 16-битного переполнения.
- Cancel/error не оставляют temp.
- Существующий файл не изменяется.
- После дочерней программы Commander продолжает принимать команды.
## 10. Фаза P5 — стабилизация PoC 0.1
### Обязательная матрица
| Область | Сценарии |
|---|---|
| Каталог | пустой, один файл, 27/28, synthetic 640/641, DSS >512 физических записей |
| Навигация | границы, repeat, две разные глубины каталогов |
| Файлы | 0, 1, 4095, 4096, 65535, 65536, более 1 МБ |
| Ошибки | ENOENT, EACCES/readonly, ENOSPC, смена носителя |
| Copy cleanup | cancel в начале/середине/конце, ошибка read/write/rename |
| EXEC | успех, отсутствующий файл, плохой EXE, возврат с другим экраном |
| Ресурсы | 100 refresh, 20 copy, повторные запуск/выход |
| Экран | полный redraw, dirty rows, диалог, возврат из EXE |
### Артефакты релиза PoC
- `SPRCMD.EXE`;
- Makefile с `floppy`, `hdd` и `run`;
- MAME-тесты и контрольные скриншоты;
- test files с известными размерами/checksum;
- отчёт размеров HOME/банков;
- список известных ограничений;
- инструкция запуска.
### Release gate 0.1
Все десять критериев раздела 10 требований выполнены. Наличие известной
ошибки, способной повредить чужой файл или потерять EMM-блок, блокирует релиз.
Release gate выполнен в MAME 0.288 / BIOS 3.06 / DSS 1.71.57. Сводка,
команды воспроизведения и ограничения зафиксированы в
[p5-stability-results.md](p5-stability-results.md) и
[poc-0.1-release.md](poc-0.1-release.md). Проверка на реальном Sprinter не
подменяется MAME и остаётся обязательным последующим smoke-тестом.
## 11. Версия 0.2 — безопасное файловое ядро
### Цель
Сделать Commander пригодным для реальной работы с файлами без viewer/editor.
### Функции
- одна EMM-страница на панель и предел 640 записей сохраняются;
- `Insert`, `*`, `+`/`-` и маски выбора — выполнены;
- сортировки name/ext/size/date и reverse — выполнены и проверены в MAME;
- последовательная очередь выбранных файлов F5 — выполнена;
- рекурсивное копирование одного каталога и группы directory roots —
выполнено; traversal hardening для больших деревьев остаётся;
- F6 local rename — выполнено; межкаталожный/group move остаётся в работе;
- F7 mkdir — выполнено и проверено в MAME;
- F8 single file/empty-directory delete и recursive group delete выбранных
файлов и каталогов — выполнены, включая runtime-cancel; защитная policy
остаётся;
- cancel между файлами и блоками;
- overwrite/skip/rename и варианты all — выполнены для файлов, включая
backup/rollback и readonly re-prompt; merge существующих каталогов позже;
- проверка свободного места;
- сохранение даты, времени и атрибутов обычных файлов — выполнено и
проверено exact FAT-тестом; метаданные каталогов не входят в P18;
- защита readonly при overwrite — выполнена; delete/move policy остаётся;
- подсчёт итогов выбранной группы;
- переключение дисков минимум через диалог.
### Внутренние работы
- сохранить одностраничный store и добавить к `ScPanel` ключ и направление сортировки;
- расширить `ScJob` очередью и стеком каталогов;
- сохранить crash-safe temp/commit модель;
- добавить единый policy object для ошибок;
- определить поведение при частично успешной группе.
### Критерий выхода 0.2
- Неделя dogfood без повреждения данных.
- Рекурсивные операции проходят дерево глубиной не менее 16 уровней.
- Рекурсивная job обрабатывает более 640 файлов суммарно в нескольких
каталогах, не превращая саму панель в многостраничную.
- Cancel оставляет уже завершённые файлы в определённом документированном
состоянии и не оставляет temp.
- Все разрушительные команды требуют подтверждения.
## 12. Версия 0.4 — рабочая оболочка
### Функции
- однострочная командная строка;
- история команд и каталогов;
- интерактивный полноэкранный pseudo-shell по `Ctrl+O`;
- вставка имени/пути активной и пассивной панели;
- запуск `.EXE` с параметрами;
- file associations;
- пользовательское меню;
- F3 через отдельный viewer;
- F4 через отдельный editor;
- F9 и базовое меню;
- режимы панелей `Brief`/`Details` по образцу VC;
- конфигурационный файл;
- сохранение путей, sort mode и палитры;
- цветовые правила по расширениям; без файла: каталоги белые, EXE жёлтые,
остальные файлы светло-серые;
- `Ctrl+U`, `Ctrl+O`, root;
- полноценный drive dialog;
- краткая встроенная помощь.
### Протокол helper-программ
До реализации F3/F4 фиксируется небольшой контракт:
- полный путь передаётся аргументом;
- код возврата различает успех, отмену и ошибку;
- helper не обязан восстанавливать экран;
- Commander всегда восстанавливает режим и перечитывает затронутую панель;
- для editor предусматривается признак изменения файла.
### Критерий выхода 0.4
Commander можно использовать как основную оболочку DSS для навигации,
операций, запуска программ, просмотра и редактирования файлов.
## 13. Версия 0.6 — возможности VC-класса
### Функции
- quick search по текущей панели;
- постоянные фильтры/маски панели;
- quick-view panel;
- info panel;
- размеры каталогов;
- сравнение каталогов;
- поиск файлов по имени;
- поиск текста в файлах, если скорость приемлема;
- виртуальная панель результатов поиска;
- дерево каталогов;
- история посещённых каталогов;
- descriptions (`description.ion`/настраиваемое имя), если формат выбран;
- изменение атрибутов и времени через UI.
### Архитектурный gate
Перед началом поиска должен быть стабилен интерфейс `ScSource`. Результаты
поиска не должны притворяться настоящим каталогом с помощью специальных
проверок по всему UI: они реализуются как отдельный source kind.
### Критерий выхода 0.6
Поиск и дерево работают на объёмах, превышающих near-memory, а возврат из
виртуальной панели не теряет прежнее состояние настоящей панели.
## 14. Версия 0.8 — виртуальные источники и архивы
### Обязательные исследования
- какие архиваторы реально существуют для Sprinter;
- можно ли получить листинг без распаковки;
- достаточно ли внешнего процесса и list-файла;
- какие форматы стоит поддерживать первыми;
- сколько памяти требует нативный decoder;
- разрешает ли лицензия включать выбранную реализацию.
### Предпочтительная последовательность
1. Внешний архиватор по шаблону команды.
2. Просмотр архива как read-only `SC_SOURCE_ARCHIVE`.
3. Извлечение выбранных файлов.
4. Копирование файла в архив, если внешний инструмент поддерживает.
5. Только затем — нативный decoder для одного востребованного формата.
### Дополнительные функции
- мышь;
- локализация строк через resource file;
- расширенная помощь;
- bookmarks;
- protocol внешних plugin/helper-программ;
- сохранённые наборы конфигурации.
### Критерий выхода 0.8
Архивная и поисковая панели используют тот же panel UI без специальных
ветвей в renderer и навигации. Ошибка внешнего архиватора не повреждает
исходный архив.
## 15. Версия 1.0 — релизная стабилизация
К этому этапу уже должны быть закончены все назначенные функции 0.2–0.8.
Отдельно проверяются три обязательных релизных контракта:
- командная строка и интерактивный полноэкранный pseudo-shell по `Ctrl+O`;
- сортировка каждой панели по имени, расширению, размеру и дате, по
возрастанию и убыванию;
- подсветка по типу файла с настраиваемыми цветами и заданным поведением
при отсутствии конфигурации.
Предел панели 640 записей в 1.0 остаётся штатным документированным
ограничением.
### Надёжность
- проверка всех cleanup-path;
- защита от двойного освобождения;
- тесты нехватки EMM;
- тесты предела файловых дескрипторов;
- смена и извлечение носителя;
- read-only и disk full;
- повреждённые каталоги/архивы;
- отмена каждой длительной операции;
- безопасное обновление конфигурации через temp/rename.
### Производительность
- профилирование чтения больших каталогов;
- профилирование сортировок EMM;
- минимизация переключений W3;
- группировка dirty rows;
- размер блока copy по фактическим измерениям;
- размер HOME и каждого банка;
- время холодного запуска и возврата из helper.
### Совместимость
- MAME 0.287 / BIOS 3.06;
- реальный Sprinter Sp2000;
- DSS 1.71.57;
- floppy и HDD;
- минимум две конфигурации доступной EMM;
- CP866 и Rus/Lat состояние клавиатуры.
### Документация релиза
- руководство пользователя;
- полный keymap;
- описание конфигурации;
- восстановление после прерванной операции;
- ограничения файловой системы;
- лицензии и благодарности;
- руководство сборки;
- архитектурная памятка для добавления source/job/bank.
### Release gate 1.0
- Нет известных дефектов потери или скрытой порчи данных.
- Все обязательные функции требований 1.0 реализованы; перенос любой из них
требует нового явного решения о составе релиза.
- Пройдены автоматические MAME-тесты и ручной hardware checklist.
- Размеры HOME/банков имеют документированный запас.
- Чистая сборка воспроизводима штатной командой проекта.
## 16. Версия 2.0+
Первый архитектурный рубеж после 1.0 — просмотр каталогов, содержащих более
640 записей. Перед реализацией сравниваются два варианта.
**Вариант A: потоковые логические страницы.** Рекомендуемый первый шаг:
- в EMM по-прежнему находится только текущая страница и не более 640 строк;
- виртуальные `<<< PAGE n` и `>>> PAGE n` занимают места в этом лимите;
- при переходе каталог сканируется заново до требуемой логической позиции;
- каталоги и файлы отбираются двумя проходами, чтобы каталоги оставались
первыми во всём потоке;
- сортировка выполняется только внутри текущей страницы и так обозначается
в UI;
- изменение каталога между проходами сбрасывает просмотр на первую страницу.
Текущий DSS API `F_FIRST/F_NEXT` не принимает ключ или направление
сортировки. Незавершённые итераторы двух панелей не сохраняются: каждый
переход заканчивает перебор либо начинает новый.
Платформенная предпосылка варианта A: стабильный DSS 1.71 не умеет перейти
за 512-ю физическую FAT-запись и возвращает код 35. Потоковые страницы
становятся реализуемыми только после перехода на расширенный режим
`F_FIRST` (`B=0x80/0x81`) из экспериментальной ветки `beta_cdfs` либо после
эквивалентного исправления DSS. До этого `>>> MORE...` является только
информирующим маркером, а не переходом на страницу 2. Детали:
[dss-large-directories.md](dss-large-directories.md).
**Вариант B: динамический EMM-store.** Он нужен, только если потребуются
глобальная сортировка и произвольный переход между страницами:
- выбор модели 1024, 2048 или динамического предела по результатам замеров;
- выделение и освобождение динамической цепочки EMM-страниц;
- межстраничная сортировка или индекс без полной копии списка в W2;
- определённое поведение при нехватке EMM на части каталога;
- нагрузочные тесты многостраничных списков и отмены во время scan/sort.
Для обоих вариантов виртуальные записи имеют отдельный тип и не участвуют в
выделении, файловых операциях и сортировке. Точный номер версии после 1.0
фиксируется отдельным release gate; базовый релиз 1.0 от этого не зависит.
Прочий backlog, не влияющий на релиз 1.0:
- нативные архивные codecs;
- FTP/serial/network source при наличии транспорта;
- disk image source;
- сравнение и синхронизация деревьев;
- макросы;
- очередь нескольких jobs;
- background copy, только если появится безопасная модель многозадачности;
- интеграция viewer/editor в банки Commander;
- дополнительные режимы экрана.
DOS-специфические функции DN — CD player, modem terminal, disk recovery,
spreadsheet, games и desktop окон — остаются отдельными приложениями.
## 17. Правило изменения плана
После каждого release gate обновляются:
1. Фактическое время этапа.
2. Размер HOME и банков.
3. Число обязательных EMM-страниц.
4. Открытые платформенные риски.
5. Состав следующей версии.
Новая функция не добавляется в текущий этап без одного из решений:
- она устраняет блокирующий риск текущего release gate;
- она дешевле сейчас из-за уже изменяемого интерфейса;
- другая функция того же объёма явно исключается из этапа.
Такой обмен фиксируется в этом документе, чтобы PoC и 1.0 не расширялись
неуправляемо.
@@ -0,0 +1,100 @@
# DSS: большие каталоги и `F_FIRST/F_NEXT`
## Вывод
Стабильный DSS 1.71 не может перечислить каталог за пределами первых 512
физических FAT-записей. Это ограничение внутреннего 16-КБ кэша DSS, а не
размера `ffblk_t`, адреса DTA, W2, W3 или EMM-хранилища Commander.
Commander хранит до 640 элементов в EMM, но на стабильном DSS источник
может закончиться раньше. Код ошибки 35 принимается как частичный успех:
полученные элементы сохраняются, а последней добавляется красная виртуальная
строка `>>> MORE...`.
## Подтверждение исходниками DSS
Исходники находятся в `../../docs/sources/Estex-DSS` и закодированы в CP866.
Для чтения комментариев они просматривались через `iconv`, без изменения
оригинальных файлов.
Стабильная ветка `master`, релизный срез `6078563`:
- `DSS/defines.inc`: `DIRPAGE.buffer = 0xC000`;
- `DSS/FS/FAT.asm:LOADDIR`: за один раз читается ровно `0x4000` байт;
- размер `FAT_DIRECTORY_RECORD` равен 32 байтам;
- `SEARCH.Custom` начинает с `IX=0xC000` и прибавляет 32;
- после 512 записей IX переходит через `0xFFFF`, а
`SEARCH.error_too_many_files` возвращает
`DSS_Error.sys.TOO_MANY_FILES_IN_DIR`;
- `DSS/API/Find.asm:F_NEXT` отдельно проверяет `IX==0` и направляет выполнение
на ту же ошибку;
- комментарии `TODO` прямо требуют record index и загрузку другой части
каталога размером более `0x4000` байт.
В системном `SYSTEM.DOS`, реально используемом MAME, найдены обе характерные
последовательности стабильной реализации: проверка `LD IX,0` и цикл
`ADD IX,32` до переноса. Бинарник на HDD и `dss171u.img` совпадают по SHA-256.
## Почему старый тест показывал 682
Один ранний экран `P3DIR` показывал `count=682, errno=0`, но поле `last`
содержало `B0000599.DAT`. Проверка физического порядка тестового FAT-образа
показала:
- записи 0 и 1 — `.` и `..`;
- `B0000599.DAT` — запись 511, то есть 512-я физическая запись;
- запись 512 уже имеет другое имя.
Следовательно, DSS завершил реальный перебор точно на системной границе, а
старый диагностический `.EXE` исказил локальный счётчик и `errno`. Этот экран
не является доказательством чтения 682 записей. Исправленная проба хранит
счётчик и контрольные имена 511/512/513 в статической памяти.
Матрица `tests/p3_enum_matrix` независимо получила для всех проверенных
вариантов `count=512, end_errno=35`: обычный wrapper, wrapper и цикл в W1,
чтение W3 и запись в EMM.
## Семантика Commander 0.11.0
При `fnext() == -1 && errno == 35`:
1. Ошибка не откатывает уже прочитанный каталог.
2. `panel.truncated` устанавливается в 1.
3. В конец добавляется `ScDirEntry` с флагом `SC_ENTRY_DSS_LIMIT` и именем
`>>> MORE...`.
4. Маркер всегда сортируется после файлов, рисуется красным и занимает один
из 640 слотов панели.
5. `Enter` и файловые команды не трактуют маркер как файл или каталог.
Число видимых файлов может быть меньше 510: в 512 физических записей входят
`.`/`..`, удалённые записи и служебные записи длинных имён, которые DSS или
Commander отфильтровывают.
## Расширенная ветка DSS
В `origin/beta_cdfs` существует незавершённая реализация больших каталогов:
- `B=0x80` — короткое имя и обход без старого предела;
- `B=0x81` — DOS-имя и обход без старого предела;
- хранится record index;
- при окончании страницы вызывается `LOAD_NEXT_DIR_PART_TO_DIR_CACHE`;
- старые режимы `B=0/1` намеренно ограничиваются 512 результатами для
совместимости.
Это хороший upstream-прототип для версии 2.0, но не контракт стабильного DSS
1.71. До отдельной сборки и тестирования Commander не должен зависеть от
него.
## Следствие для логических страниц Commander
Предложенные `<<< PAGE n`/`>>> PAGE n` реализуемы без дополнительной памяти,
если DSS способен продолжить физический перебор после первой 16-КБ части.
На стабильном DSS повторный `F_FIRST/F_NEXT` снова остановится на тех же 512
записях, поэтому настоящее переключение на PAGE2 требует одного из вариантов:
1. стабилизировать расширенный режим `B=0x80/0x81`;
2. добавить эквивалентный API DSS с record index/offset;
3. как нежелательный запасной путь — реализовать raw FAT source вне DSS.
Выбор делается после релиза 1.0; текущий красный маркер уже имеет отдельный
тип и сможет позднее стать переходом без изменения формата `ScDirEntry`.
@@ -0,0 +1,324 @@
# Sprinter Commander: платформенные пробы P1
Статус: MAME 0.287 / BIOS 3.06 — `PASS`; проверка на реальном Sprinter ожидается
Связанные документы:
[требования](commander-requirements.md),
[архитектура](commander-architecture.md),
[клавиши](commander-keymap.md),
[roadmap](commander-roadmap.md),
[экранный backend P2](p2-screen-backend.md)
## 1. Назначение
Программа `p1probe.exe` объединяет платформенные проверки, которые должны
быть завершены до реализации UI Commander. Она не является ранней версией
Commander и не определяет его пользовательский интерфейс.
Исходники находятся в `tests/p1_platform/`.
Вторая программа, `p1child.exe`, используется только для проверки ESTEX
`EXEC`, возврата к родителю и кода завершения.
## 2. Ограничения безопасности
- Пробы не создают, не изменяют и не удаляют файлы.
- Каталожная проба выполняет только `getcwd`, `chdir(".")` и
`ffirst/fnext`.
- EMM-проба пишет только в выделенный ей блок из трёх страниц.
- Screen-проба пишет только в отдельную выделенную EMM-страницу.
- Дочерняя программа меняет видеорежим, но не пишет на диск.
- Проба не открывает файловые дескрипторы приложения напрямую.
- MAME не запускается целями `make` и `make all`.
Первый MAME-прогон выполнен 6 сентября 2026 года после отдельного согласования.
Все запуски использовали автоматически пересоздаваемый `mc.img`; исходные
дисковые образы не изменялись программами-пробами.
## 3. Состав
| Файл | Назначение |
|---|---|
| `p1probe.c` | меню, общие сообщения, последовательность тестов |
| `p1_sys.c` | app-local wrappers DSKINFO, EXEC, WAIT, WINREST |
| `p1_emm.c` | выделение трёх страниц и проверка границ |
| `p1_dir.c` | CURDIR/CHDIR/F_FIRST/F_NEXT и DSKINFO output |
| `p1_screen.c` | полный и частичный WINREST с ненулевым offset |
| `p1_keyboard.c` | вывод `ascii/scan/live modifiers` |
| `p1_exec_test.c` | два режима EXEC и проверка состояния родителя |
| `p1child.c` | дочерний EXE, меняющий режим на 40x32 и возвращающий `0x5A` |
| `mame_dump_keys.lua` | вывод карты полей AT-клавиатуры MAME |
| `mame_p1_keyboard.lua` | воспроизводимый ввод стрелок, F1–F10 и сочетаний |
| `Makefile` | сборка обоих EXE |
## 4. Сборка
```sh
cd applications/Volkov/tests/p1_platform
make
```
Диагностическая программа собирается в режиме `small --safe`. Это сделано
намеренно: P1 проверяет платформенные ABI и не должен одновременно отлаживать
банковую topology Commander. Сборка `big --safe` является критерием P2.
Дочерняя программа собирается как `tiny --safe`.
### Результат текущей сборки
`p1probe.exe`:
- `_CODE`: 11 062 байта;
- данные: 2 741 байт;
- файл EXE: 11 835 байт;
- heap после статики: 17 429 байт;
- стек: 1 279 байт.
`p1child.exe`:
- `_CODE`: 3 907 байт;
- данные: 849 байт;
- файл EXE: 4 453 байта;
- heap после статики: 10 092 байта;
- стек: 1 279 байт.
Компиляция выполнена без ошибок. Значения относятся к текущей диагностической
сборке и не являются бюджетом Commander.
## 5. Статическая проверка ABI
### 5.1. DSKINFO
Сигнатура:
```c
int p1_disk_info(uint8_t disk, P1DiskInfo *out);
```
Сгенерированный вызов передаёт `disk` в A, `out` в DE. Wrapper сохраняет IX,
а после RST сохраняет:
- A — sectors per cluster;
- HL — total clusters;
- DE — free clusters;
- BC — bytes per sector.
Размер `P1DiskInfo` проверяется typedef-assert и равен семи байтам.
### 5.2. EXEC
Сигнатура намеренно имеет 8-битный аргумент первым:
```c
int p1_exec(uint8_t path_mode, const char *path);
```
Для этого порядка SDCC передаёт `path_mode` в A и `path` в DE. Wrapper
перекладывает их в B и HL соответственно. Вариант
`p1_exec(const char *path, uint8_t mode)` был отвергнут после просмотра
сгенерированного ASM: второй 8-битный аргумент SDCC помещал на стек, а не в
DE.
На успехе A сохраняется как `p1_exec_exit_code`; на ошибке код из A проходит
через `__errno_set`.
### 5.3. WINREST
Wrapper повторяет уже подтверждённую раскладку `tests/winrest` и `mdview2`:
```text
A row
L column
SP+2 height
SP+3 width
SP+4 physical page
SP+5..6 offset in 16-KB page
```
IX сохраняется, перед RST устанавливается в `0xC000 + offset`. Сгенерированный
вызов проверен для полного экрана `32x80` и окна `2x20` с offset `0x2000`.
## 6. Меню `p1probe.exe`
| Клавиша | Проба | Автоматическая оценка |
|---|---|---|
| `1` | Каталог и F_FIRST/F_NEXT | да, плюс наблюдаемые коды |
| `2` | EMM: три страницы | да |
| `3` | WINREST full/partial | только завершение; изображение оценивается визуально |
| `4` | Клавиатурные коды | сбор наблюдений |
| `5` | EXEC и возврат | да для режима B=1 |
| `6` | DSKINFO текущего диска | да |
| `A` | Пробы 1, 2 и 6 | да |
| `0`/`Esc` | Выход | — |
## 7. Ожидаемые проверки
### 7.1. Каталог
Проба дважды перечисляет каталог по маскам `*.*` и `*`, печатает первые 20
записей и итоговые значения:
- число записей;
- наличие `.`;
- наличие `..`;
- `errno` последнего `fnext()`;
- совпадение результатов двух масок;
- сохранение CWD после `chdir(".")`.
Фактический результат DSS различается: `*.*` вернул обе записи с расширением,
а `*` завершился как пустой список с `ENOENT`. Поэтому `*.*` принят как
обязательный шаблон полного scan; совпадение масок не предполагается.
MAME-прогон установил фактическое завершение перечисления: `fnext()`
возвращает ошибку с `errno=3` (`ENOENT`). Код `0x0F` из старого описания DSS
для этой операции не подтверждён. Проба после измерения принимает только
`ENOENT`, чтобы возможная смена поведения не осталась незамеченной.
### 7.2. EMM
Проверяются:
- `mem_alloc_pages(3)`;
- три различных физических номера;
- первые 64 байта каждой страницы;
- последние 64 байта каждой страницы;
- независимые patterns страниц;
- восстановление `mem_info.free_pages` после освобождения блока.
### 7.3. Screen/WINREST
Первый вызов должен показать полный экран 80x32 с рамкой и цветными строками.
После клавиши второй вызов должен наложить окно 20x2 в позиции row 14,
column 30. Данные второго окна лежат по offset `0x2000`, поэтому тест
одновременно проверяет корректность IX.
Тест не открывает окно через BIOS: ESTEX WINREST здесь только копирует
прямоугольник в текущий текстовый экран. Повтор со снимками в `t=20` и `t=24`
без промежуточных вызовов подтвердил, что фон вне прямоугольника сохранён.
Первая версия pattern использовала арифметически полученные атрибуты
`0x11..0x17`. Верхняя граница `0x11` была обычным blue-on-blue, а часть
остальных сочетаний выглядела неоднозначно в разных моментах палитрового
цикла. Замечание о синих символах на синем фоне подтвердилось; raw-арифметика
атрибутов в UI запрещается.
После замены на именованные `COLOR(LIGHTBLUE..WHITE, BLUE)` полный экран и
частичное окно были видимы на кадрах `t=20` и `t=24`. Повторная проверка
исходных PNG установила, что их декодированные RGB-пиксели полностью
идентичны. Прежнее сообщение о чередовании P2 было ошибкой встроенного
предпросмотра, а не поведением MAME.
Отдельная проба P2.2 сохранила, установила, прочитала обратно и восстановила
шесть атрибутов во всех четырёх планах; многоцветная сцена осталась стабильной.
Строка P1-SCR-03 ниже подтверждена как `PASS`. Детали — в
[результатах P2.2](p2-palette-results.md).
### 7.4. Keyboard
Для 24 событий печатаются:
```text
ascii, scan, полное kbd_mod_state, compact Shift/Ctrl/Alt
```
`Esc` печатается последним и завершает сбор. В автоматическом MAME-сценарии
использован следующий набор из 24 событий:
- стрелки, Home/End, PgUp/PgDn;
- F1F10;
- Insert/Delete;
- Ctrl+R;
- Ctrl+PgUp;
- Alt+F1;
- Esc.
### 7.5. EXEC
Выполняются два случая:
1. `B=0`, `P1CHILD.EXE` — наблюдение short-name/PATH semantics.
2. `B=1`, `.\\P1CHILD.EXE` — обязательный случай PoC.
Дочерняя программа:
- переключается в 40x32;
- показывает свои W1/W2/W3 и CWD;
- ждёт клавишу;
- возвращает `0x5A`.
После возврата родитель проверяет:
- результат непосредственного EXEC;
- результат WAIT;
- восстановление W1/W2/W3;
- восстановление CWD;
- возможность вернуть 80x32 и продолжить работу.
Ошибка случая B=0 записывается как наблюдение и не проваливает PoC. Ошибка
B=1 проваливает тест.
## 8. Выполненный порядок первого прогона
1. `2` — EMM.
2. `6` — DSKINFO.
3. `1` — каталог.
4. `3` — WINREST.
5. `4` — клавиатура.
6. `5` — EXEC последним.
7. Повторно `2` в том же процессе после EXEC, чтобы проверить EMM.
8. Возврат в меню; независимые запуски дополнительно подтвердили повторный
старт программы.
Такой порядок сначала проверяет неразрушительные примитивы и оставляет
наиболее сложное переключение контекста на конец.
## 9. Таблица runtime-результатов
Окружение MAME: `mame.arm` 0.287 (`b0c4527c`), машина `sprinter`, BIOS `v3.06`, запуск
`p1probe.exe` с дискеты A:. `PENDING` в аппаратной колонке означает только
отсутствие прогона на реальном Sprinter и не отменяет результата MAME.
| ID | Проверка | MAME 0.287 / BIOS 3.06 | Реальный Sprinter | Артефакт |
|---|---|---|---|---|
| P1-DIR-01 | CWD и `chdir(".")` | PASS | PENDING | [результат](../artifacts/mame-p1/directory/strict-enoent.png) |
| P1-DIR-02 | `*.*` против `*` | PASS: `2` против `0`; полный scan использует только `*.*` | PENDING | [результат](../artifacts/mame-p1/directory/strict-enoent.png) |
| P1-DIR-03 | Код конца F_NEXT | PASS, `ENOENT=3` | PENDING | [строгая проверка](../artifacts/mame-p1/directory/strict-enoent.png) |
| P1-EMM-01 | Блок 3 страницы | PASS, страницы `EF/F0/F1` | PENDING | [результат](../artifacts/mame-p1/emm/result.png) |
| P1-EMM-02 | Границы страниц | PASS | PENDING | [результат](../artifacts/mame-p1/emm/result.png) |
| P1-EMM-03 | Счётчик после free | PASS, `221 → 221` | PENDING | [результат](../artifacts/mame-p1/emm/result.png) |
| P1-SCR-01 | Полный WINREST 80x32 | PASS, визуально | PENDING | [контрастный экран](../artifacts/mame-p1/winrest/full-contrast.png) |
| P1-SCR-02 | Offset WINREST | PASS, row 14 / col 30 / `0x2000` | PENDING | [фон и окно вместе](../artifacts/mame-p1/winrest/partial-contrast-stable.png) |
| P1-SCR-03 | Цветные атрибуты в двух моментах цикла | PASS, RGB кадров идентичен; P2.2 подтвердил 6 атрибутов × 4 плана | PENDING | [кадр 1](../artifacts/mame-p1/winrest/partial-flash-phase.png), [кадр 2](../artifacts/mame-p1/winrest/partial-contrast-stable.png), [P2.2](p2-palette-results.md) |
| P1-SCR-04 | Raw-атрибуты `0x11..0x17` | OBSERVED, raw-арифметика запрещена | PENDING | [неудачный исходный pattern](../artifacts/mame-p1/winrest/partial-phase-hidden.png) |
| P1-KBD-01 | Базовые scan-коды | PASS | PENDING | [таблица](../artifacts/mame-p1/keyboard-commander/sprinter/0002.png) |
| P1-KBD-02 | Ctrl/Alt modifiers | PASS | PENDING | [сочетания](../artifacts/mame-p1/keyboard-commander/sprinter/0003.png) |
| P1-EXE-01 | EXEC B=0 | PASS | PENDING | [результат](../artifacts/mame-p1/exec/result.png) |
| P1-EXE-02 | EXEC B=1 | PASS | PENDING | [результат](../artifacts/mame-p1/exec/result.png) |
| P1-EXE-03 | Exit/WAIT `0x5A` | PASS / PASS | PENDING | [результат](../artifacts/mame-p1/exec/result.png) |
| P1-EXE-04 | Восстановление W1/W2/W3 | PASS, `F3/F2/FF` | PENDING | [результат](../artifacts/mame-p1/exec/result.png) |
| P1-EXE-05 | EMM после двух EXEC | PASS | PENDING | [контроль](../artifacts/mame-p1/exec-then-emm/emm-result.png) |
| P1-DSK-01 | DSKINFO | PASS | PENDING | [результат](../artifacts/mame-p1/dskinfo/result.png) |
### 9.1. Зафиксированные значения
- DSKINFO: 1 сектор на кластер, 512 байт на сектор, 2847 кластеров всего,
2814 свободно.
- EMM до выделения: 256 страниц всего, 221 свободна; после освобождения
снова 221.
- Оба режима EXEC: `rc=0`, `errno=0`, непосредственный exit=`5A`, WAIT=`5A`.
- CWD до и после child: `\`; страницы W1/W2/W3 до и после: `F3/F2/FF`.
- Модифицированная клавиша приходит с установленным битом 7 scan-кода:
Ctrl+R=`93`, Ctrl+PgUp=`D9`, Alt+F1=`BB`. После `scan & 0x7F` получаются
базовые коды `13`, `59`, `3B`; compact modifiers равны `02`, `02`, `04`.
- Esc: `ascii=1B`, `scan=01`.
## 10. Условия завершения P1
MAME-часть P1 завершена: все обязательные строки имеют `PASS`, фактические
клавиатурные коды перенесены в keymap, завершение `fnext()` уточнено,
`EXEC B=1`/WAIT и EMM после child подтверждены, артефакты сохранены.
Проверка на реальном Sprinter остаётся желательной аппаратной валидацией,
но не блокирует переход к P2 и реализацию platform API PoC. Если реальное
железо даст расхождение, оно оформляется отдельным репро и не маскируется
адаптацией UI.
@@ -0,0 +1,77 @@
# Sprinter Commander: локальный rename 0.2/P10
Дата: 8 сентября 2026 года.
Статус: `PASS` в MAME 0.288 / BIOS 3.06 / DSS 1.71.57 на HDD `D:`.
## Реализованный срез F6
`F6` переименовывает одну текущую обычную запись внутри активного каталога.
Поддержаны как файлы, так и каталоги. Диалог начинает с текущего имени и
использует общий редактор; новое имя проходит тот же строгий validator одного
компонента DOS 8.3, что и F7.
Это пока локальный `Rename`, а не полный VC `Move/Rename`:
- межкаталожный move в пассивную панель ещё не выполняется;
- если в панели есть неоднозначная группа, команда явно отказывается от
операции; единственная выбранная текущая запись допустима;
- `..` и маркер предела DSS никогда не передаются файловой системе;
- совпадающее без учёта ASCII-регистра имя считается no-op.
Низкоуровневый `sc_rename_local()` учитывает подтверждённое ограничение DSS
1.71: `RENAME` принимает два basename относительно CWD. Helper сохраняет CWD,
временно входит в активный каталог, проверяет отсутствие target через
`F_FIRST`, выполняет `rename()` и восстанавливает CWD. Таким образом
существующая запись не перезаписывается даже при иной семантике конкретной
версии DSS.
## Проверка
`RENRT.EXE` вызывает тот же helper и проверяет:
- rename файла с побайтно неизменным содержимым;
- отказ при существующем target и сохранность обоих файлов;
- rename непустого каталога с сохранностью вложенного файла;
- CWD после успеха, collision и ошибки входа в каталог.
После UI-сценария внешний checker извлекает HDD и подтверждает окончательные
имена и содержимое:
```text
PASS: file/dir rename, no-overwrite, CWD restore и UI F6.
```
Контрольные состояния UI:
- [диалог с исходным именем](../artifacts/mame-p10/rename/dialog-default.png);
- [`FINAL.BIN` создан и остался под cursor](../artifacts/mame-p10/rename/renamed-focused.png);
- [неизменное имя обработано как no-op](../artifacts/mame-p10/rename/unchanged-name.png);
- [существующий target отвергнут](../artifacts/mame-p10/rename/collision.png);
- [`..` не переименовывается](../artifacts/mame-p10/rename/virtual-guard.png);
- [неоднозначная группа не превращается в rename cursor](../artifacts/mame-p10/rename/group-guard.png);
- [штатный возврат в DSS](../artifacts/mame-p10/rename/clean-dss-return.png).
## Размер
Конфигурация `--memory big --safe --max-allocs 3000`:
| Область | После P9 + UI footer | После P10 | Изменение |
|---|---:|---:|---:|
| `_CODE` W2 | 9449 | 9463 | +14 |
| данные W2 | 3256 | 3256 | 0 |
| свободная куча W2 | 2143 | 2129 | -14 |
| BANK1 | 6477 | 6477 | 0 |
| BANK2 | 8303 | 10008 | +1705 |
| `SPRCMD.EXE` | 43026 | 43040 | +14 |
В BANK2 остаётся 6376 байт, в BANK1 — 9907 байт. Межбанковый аудит чист.
## Воспроизведение
```sh
make hdd-p10-rename ALLOCS=3000
tests/run_mame_hdd.sh build/hdd/p10_rename.chd \
tests/mame_p10_rename.lua artifacts/mame-p10/local 65
tests/check_p10_rename_hdd.sh build/hdd/p10_rename.chd
```
@@ -0,0 +1,71 @@
# Sprinter Commander: однообъектный delete 0.2/P11
Дата: 9 сентября 2026 года.
Статус: `PASS` в MAME 0.288 / BIOS 3.06 / DSS 1.71.57 на HDD `D:`.
## Реализованный срез F8
`F8` удаляет одну текущую обычную запись только после модального
подтверждения:
- обычный файл удаляется через `unlink()`;
- пустой каталог — через `rmdir()`;
- непустой каталог сохраняется и даёт управляемую ошибку;
- `N`/`Esc` отменяют команду без изменения носителя;
- `..` и маркер предела DSS не передаются файловой системе;
- в срезе P11 неоднозначная выбранная группа отклонялась; начиная с P13
выбранные файлы удаляются группой, а выбранные каталоги пропускаются.
Это исторический контракт P11/P13; начиная с P17 каталоги выбранной группы
удаляются рекурсивно.
Рекурсивное и групповое удаление намеренно не имитируются циклом вокруг
текущего cursor. Они будут реализованы общей файловой job с определённой
политикой частичного успеха и cancel.
## Проверка
Target-проба `DELRT.EXE` проверяет удаление обычного файла и пустого каталога
по полным путям, отказ для непустого каталога, сохранность соседнего файла и
неизменность CWD. UI-сценарий затем проверяет обе стороны подтверждения и
защитные ветки. После выхода дерево извлекается из CHD и проверяется
побайтно:
```text
PASS: file/empty-dir delete, cancel, guards, nonempty и CWD.
```
Контрольные состояния:
- [подтверждение удаления файла](../artifacts/mame-p11/delete/file-confirm.png);
- [cancel сохранил файл](../artifacts/mame-p11/delete/cancel-preserves.png);
- [файл удалён после подтверждения](../artifacts/mame-p11/delete/file-deleted.png);
- [пустой каталог удалён](../artifacts/mame-p11/delete/empty-dir-deleted.png);
- [непустой каталог сохранён](../artifacts/mame-p11/delete/nonempty-preserved.png);
- [`..` заблокирован](../artifacts/mame-p11/delete/virtual-guard.png);
- [неоднозначная группа заблокирована](../artifacts/mame-p11/delete/group-guard.png);
- [штатный возврат в DSS](../artifacts/mame-p11/delete/clean-dss-return.png).
## Размер
Конфигурация `--memory big --safe --max-allocs 3000`:
| Область | После P10 | После P11 | Изменение |
|---|---:|---:|---:|
| `_CODE` W2 | 9463 | 9496 | +33 |
| данные W2 | 3256 | 3256 | 0 |
| свободная куча W2 | 2129 | 2096 | -33 |
| BANK1 | 6477 | 6477 | 0 |
| BANK2 | 10008 | 10942 | +934 |
| `SPRCMD.EXE` | 43040 | 43073 | +33 |
В BANK2 остаётся 5442 байта, в BANK1 — 9907 байт. Межбанковый аудит чист.
## Воспроизведение
```sh
make hdd-p11-delete ALLOCS=3000
tests/run_mame_hdd.sh build/hdd/p11_delete.chd \
tests/mame_p11_delete.lua artifacts/mame-p11/local 67
tests/check_p11_delete_hdd.sh build/hdd/p11_delete.chd
```
@@ -0,0 +1,78 @@
# Sprinter Commander: групповое копирование файлов 0.2/P12
Дата: 9 сентября 2026 года.
Статус: `PASS` в MAME 0.288 / BIOS 3.06 / DSS 1.71.57 на HDD `D:`.
Примечание: документ фиксирует исторический срез P12, где directory roots
ещё пропускались. Начиная с P16 текущий F5 копирует их рекурсивно; fixture и
checker P12 обновлены как регрессия нового контракта, а исходные кадры ниже
оставлены для истории этапа.
## Контракт первого группового среза
Если в активной панели нет выбора, F5 сохраняет проверенное поведение
однофайлового PoC. При ненулевом выборе F5:
1. Считает выбранные обычные файлы и отдельно выбранные каталоги.
2. Последовательно копирует только файлы в порядке текущей сортировки панели.
3. Для каждого файла использует прежний crash-safe `temp -> commit` и общую
4-КБ EMM-страницу W3.
4. Проверяет Esc между блоками и файлами.
5. При cancel/error удаляет только незавершённый temp; уже завершённые файлы
сохраняются.
6. Останавливается на первой ошибке и сообщает `copied/total`, число
пропущенных каталогов и errno.
7. Перечитывает passive panel один раз после завершённых копий.
Выбранные каталоги намеренно игнорируются до появления рекурсивной job. Это
не меняет их флаг выбора и согласуется с правилом: групповые `+`, `-`, `*`
работают только над файлами.
Overwrite по-прежнему запрещён. Повторный запуск поверх уже скопированных
файлов останавливается на первом `EEXIST`; существующие targets не меняются.
## Проверка
HDD-fixture содержит выбранный вручную `ADIR` и три файла размером 10, 4097
и 0 байт. После первого F5 внешний checker подтверждает три побайтно точные
копии, отсутствие каталога и temp. Второй F5 проверяет управляемую остановку
на существующем `A.BIN`.
```text
PASS: 3 selected files copied; selected directory skipped; no temp.
```
Контрольные состояния:
- [каталог и три файла выбраны](../artifacts/mame-p12/group-copy/selected-files-and-dir.png);
- [скопировано 3/3, один каталог пропущен](../artifacts/mame-p12/group-copy/copied-and-skipped-dir.png);
- [повтор остановлен на EEXIST](../artifacts/mame-p12/group-copy/repeat-stops-eexist.png);
- [passive panel перечитана](../artifacts/mame-p12/group-copy/passive-refreshed.png);
- [штатный возврат в DSS](../artifacts/mame-p12/group-copy/clean-dss-return.png).
## Компоновка и размер
Конфигурация `--memory big --safe --max-allocs 3000`:
| Область | После P11 | После P12 | Изменение |
|---|---:|---:|---:|
| `_CODE` W2 | 9496 | 9496 | 0 |
| данные W2 | 3256 | 3256 | 0 |
| свободная куча W2 | 2096 | 2096 | 0 |
| BANK1 | 6477 | 6477 | 0 |
| BANK2 | 10942 | 12256 | +1314 |
| `SPRCMD.EXE` | 43073 | 43073 | 0 |
Пробный перенос обработчика в новый BANK3 увеличивал EXE ровно на 16384
байта при 2823 байтах полезного кода. Поэтому P12 оставлен в BANK2: там ещё
4128 байт запаса, а размер файла не вырос. Межбанковый аудит чист.
## Воспроизведение
```sh
make hdd-p12-group-copy ALLOCS=3000
tests/run_mame_hdd.sh build/hdd/p12_group_copy.chd \
tests/mame_p12_group_copy.lua artifacts/mame-p12/local 57
tests/check_p12_group_copy_hdd.sh build/hdd/p12_group_copy.chd
```
@@ -0,0 +1,78 @@
# Sprinter Commander: групповое удаление файлов 0.2/P13
Дата: 9 сентября 2026 года.
Статус: `PASS` в MAME 0.288 / BIOS 3.06 / DSS 1.71.57 на HDD `D:`.
Это исторический контракт P13. Начиная с P17 выбранные каталоги не
пропускаются, а удаляются рекурсивно после одного общего подтверждения; новый
контракт и актуальные артефакты описаны в
[p17-tree-delete-results.md](p17-tree-delete-results.md). Текущий P13 fixture
оставлен как регрессия file-only группы.
## Контракт первого группового среза F8
Если в активной панели есть выбранные записи, F8:
1. Считает выбранные обычные файлы и выбранные каталоги отдельно.
2. Если выбранных файлов нет, не открывает подтверждение и сообщает, что
рекурсивное удаление каталогов ещё не реализовано.
3. Показывает одно подтверждение для всей группы файлов.
4. После подтверждения проходит EMM-store в порядке панели и удаляет только
выбранные записи без `FA_DIREC`.
5. Останавливается на первой ошибке. Уже удалённые файлы не восстанавливаются;
итог сообщает `deleted/total`, число пропущенных каталогов и errno.
6. После хотя бы одного успешного удаления перечитывает активную панель.
Выбранные каталоги не удаляются и не обходятся рекурсивно. Это согласуется с
контрактом групповых `+`, `-`, `*`: групповые файловые команды отделяют файлы
от каталогов. При отсутствии выбора сохранён однообъектный F8 из P11: файл
удаляется через `unlink()`, пустой каталог — через `rmdir()`.
## Проверка
HDD-fixture содержит два выбранных файла, один невыбранный файл, выбранный
пустой каталог и выбранный непустой каталог. Сценарий сначала отменяет
групповое подтверждение, затем повторяет операцию с подтверждением. После
refresh отдельно выбирается только каталог: F8 обязан отказать без диалога и
без изменения носителя.
После чистого выхода MAME дерево извлекается из CHD и проверяется побайтно:
```text
PASS: selected files deleted; selected directories and KEEP preserved.
```
Контрольные состояния:
- [два файла и два каталога выбраны](../artifacts/mame-p13/group-delete/selected-files-and-dirs.png);
- [одно подтверждение группы](../artifacts/mame-p13/group-delete/group-confirm.png);
- [отмена сохранила группу](../artifacts/mame-p13/group-delete/cancel-preserves.png);
- [файлы удалены, каталоги сохранены](../artifacts/mame-p13/group-delete/files-deleted-dirs-preserved.png);
- [directory-only группа отклонена](../artifacts/mame-p13/group-delete/directory-only-refused.png);
- [штатный возврат в DSS](../artifacts/mame-p13/group-delete/clean-dss-return.png).
## Компоновка и размер
Конфигурация `--memory big --safe --max-allocs 3000`:
| Область | После P12 | После P13 | Изменение |
|---|---:|---:|---:|
| `_CODE` W2 | 9496 | 9496 | 0 |
| данные W2 | 3256 | 3256 | 0 |
| свободная куча W2 | 2096 | 2096 | 0 |
| BANK1 | 6477 | 6477 | 0 |
| BANK2 | 12256 | 13267 | +1011 |
| `SPRCMD.EXE` | 43073 | 43073 | 0 |
В BANK2 остаётся 3117 байт. HOME и размер EXE не выросли; межбанковый аудит
чист.
## Воспроизведение
```sh
make hdd-p13-group-delete ALLOCS=3000
tests/run_mame_hdd.sh build/hdd/p13_group_delete.chd \
tests/mame_p13_group_delete.lua artifacts/mame-p13/local 58
tests/check_p13_group_delete_hdd.sh build/hdd/p13_group_delete.chd
```
@@ -0,0 +1,62 @@
# Sprinter Commander: стандартные цвета имён P14
Дата: 9 сентября 2026 года.
Статус: `PASS` в MAME 0.288 / BIOS 3.06 / DSS 1.71.57 на HDD `D:`.
## Реализованный профиль без конфигурации
Renderer выбирает атрибут обычной строки по типу записи:
- каталоги и синтетическая `..` — белые;
- файлы с точным регистронезависимым расширением `.EXE` — жёлтые;
- остальные файлы, в том числе без расширения, — светло-серые;
- виртуальный маркер предела DSS остаётся красным;
- cursor активной панели сохраняет отдельную контрастную cyan-подсветку.
Цвет применяется ко всей строке записи, поэтому имя, размер и атрибуты не
расходятся визуально. `SC_ATTR_DIR` и `SC_ATTR_EXE` переиспользуют уже
сохранённые палитровые атрибуты frame/title; новый атрибут потребовался только
для обычного файла.
Настраиваемые правила по расширениям и файл конфигурации ещё не реализованы.
Текущий профиль является обязательным fallback при отсутствии такого файла.
## Проверка
Отдельный HDD содержит `SUBDIR`, `APP.EXE`, `ARCHIVE.ZIP`, `README.TXT` и
`NOEXT`. [Контрольный кадр](../artifacts/mame-p14/default-colors/file-types.png)
показывает все три класса одновременно в активной и passive панелях. После
F10 [палитра DSS восстановлена](../artifacts/mame-p14/default-colors/clean-dss-palette.png).
Внешняя проверка извлечённого CHD подтверждает, что визуальный тест не изменил
fixture:
```text
PASS: colour fixture preserved.
```
## Компоновка и размер
Конфигурация `--memory big --safe --max-allocs 3000`:
| Область | После P13 | После P14 | Изменение |
|---|---:|---:|---:|
| `_CODE` W2 | 9496 | 9503 | +7 |
| данные W2 | 3256 | 3268 | +12 |
| свободная куча W2 | 2096 | 2077 | -19 |
| BANK1 | 6477 | 6477 | 0 |
| BANK2 | 13267 | 13388 | +121 |
| `SPRCMD.EXE` | 43073 | 43080 | +7 |
Двенадцать байт DATA/BSS — сохранённые RGB трёх компонент для четырёх
палитровых планов нового атрибута. В BANK2 остаётся 2996 байт; межбанковый
аудит чист.
## Воспроизведение
```sh
make hdd-p14-default-colors ALLOCS=3000
tests/run_mame_hdd.sh build/hdd/p14_default_colors.chd \
tests/mame_p14_default_colors.lua artifacts/mame-p14/local 36
tests/check_p14_default_colors_hdd.sh build/hdd/p14_default_colors.chd
```
@@ -0,0 +1,117 @@
# Sprinter Commander: рекурсивный F5 одного каталога 0.2/P15
Дата: 9 сентября 2026 года.
Статус: основной и отрицательные сценарии `PASS` в MAME 0.288 / BIOS 3.06 /
DSS 1.71.57 на HDD `D:`.
## Реализованный контракт
Если в активной панели нет выбранной группы и cursor стоит на обычном
каталоге, F5 рекурсивно создаёт одноимённое дерево в каталоге passive panel:
- обход breadth-first, без рекурсии C-стека;
- каждый файл проходит прежний crash-safe `temp -> commit` и общий 4-КБ
copy-buffer в выделенной странице W3;
- одновременно открыты не более source и temp;
- пустые каталоги создаются;
- existing target не объединяется и не перезаписывается;
- Esc проверяется между каталогами и на каждом 4-КБ шаге файла;
- при cancel/error уже committed-файлы и созданные каталоги остаются, temp
текущего файла удаляется;
- CWD процесса восстанавливается и passive panel перечитывается после
изменения target.
Начиная с P16 выбранные каталоги также добавляются как несколько roots в ту
же очередь. Этот документ фиксирует более узкий single-root срез P15.
## Память обхода
`sc_tree` на время одной операции выделяет одну дополнительную EMM-страницу.
Первые 256 байт сохраняют исходный CWD. Остаток содержит 1008 узлов по 16
байт: `parent index + DOS 8.3 component + depth`. Абсолютный путь строится
перед операцией по цепочке parent; фиксированный предел глубины — 32, а общий
предел пути остаётся 255 символов.
4-КБ данные файла не смешиваются с traversal queue и не размещаются в W2.
W3 отображается только внутри `bank_read/write` и `bank_read_page/
bank_write_page`; вызовы сами восстанавливают прежнюю страницу.
До добавления root проверяется, что каталог passive panel не совпадает с
source root и не лежит внутри него. Это запрещает саморастущее копирование.
## Проверка
Fixture включает пустой каталог, файлы размером 0, 15, 16 и 4097 байт и
цепочку из 16 вложенных уровней. MAME сообщил точный итог:
```text
Tree copied: 4 files; 19 dirs.
```
После выхода source и target извлечены из CHD и сравнены как полные деревья:
```text
PASS: recursive tree copied exactly; depth=16; no temp.
```
Повторный F5 остановился до изменения файлов: DSS вернул `EISDIR` (`errno
15`) для уже существующего target-каталога. Контрольные состояния:
- [source TREE и пустой target](../artifacts/mame-p15/tree-copy/before.png);
- [успех 4 files / 19 dirs](../artifacts/mame-p15/tree-copy/success.png);
- [repeat остановлен на existing target](../artifacts/mame-p15/tree-copy/repeat-existing-target.png);
- [passive panel перечитана](../artifacts/mame-p15/tree-copy/passive-refreshed.png);
- [Esc удалил temp большого файла](../artifacts/mame-p15/tree-copy/cancel-clean.png);
- [target внутри source отклонён до mutation](../artifacts/mame-p15/tree-copy/self-target-refused.png);
- [чистый возврат в DSS](../artifacts/mame-p15/tree-copy/clean-dss-return.png).
Отдельный cancel-fixture начал копирование `BIG.BIN` размером 4 МБ и подал
Esc до commit. Target root остался как определённый частичный результат,
файл и temp отсутствуют, source не изменён:
```text
PASS: tree cancel removed active temp; source intact; no BIG commit.
```
Self-target fixture расположил passive panel в `SOURCE\TREE\INNER`. Операция
остановилась с `errno 14` (`EINVAL` отображается libc на родной DSS
`EUNKOP`) до первого `mkdir`; `INNER` остался пустым:
```text
PASS: target-inside-source refused before mutation.
```
## Компоновка и размер
Конфигурация `--memory big --safe --max-allocs 3000`:
| Область | После P14 | После P15 | Изменение |
|---|---:|---:|---:|
| `_CODE` W2 | 9503 | 9503 | 0 |
| данные W2 | 3268 | 3524 | +256 |
| свободная куча W2 | 2077 | 1821 | -256 |
| BANK1 | 6477 | 9687 | +3210 |
| BANK2 | 13388 | 14547 | +1159 |
| `SPRCMD.EXE` | 43080 | 43080 | 0 |
256 байт W2 — единственный `ffblk_t` traversal engine. Крупная очередь
динамическая и в эти числа не входит. BANK1/BANK2 имеют соответственно 6697
и 1837 байт запаса; межбанковый аудит чист.
## Воспроизведение
```sh
make hdd-p15-tree-copy ALLOCS=3000
tests/run_mame_hdd.sh build/hdd/p15_tree_copy.chd \
tests/mame_p15_tree_copy.lua artifacts/mame-p15/local 60
tests/check_p15_tree_copy_hdd.sh build/hdd/p15_tree_copy.chd
make hdd-p15-tree-cancel hdd-p15-tree-self ALLOCS=3000
tests/run_mame_hdd.sh build/hdd/p15_tree_cancel.chd \
tests/mame_p15_tree_cancel.lua artifacts/mame-p15/cancel 48
tests/check_p15_tree_cancel_hdd.sh build/hdd/p15_tree_cancel.chd
tests/run_mame_hdd.sh build/hdd/p15_tree_self.chd \
tests/mame_p15_tree_self.lua artifacts/mame-p15/self 48
tests/check_p15_tree_self_hdd.sh build/hdd/p15_tree_self.chd
```
@@ -0,0 +1,72 @@
# Sprinter Commander: рекурсивный group F5 0.2/P16
Дата: 9 сентября 2026 года.
Статус: `PASS` в MAME 0.288 / BIOS 3.06 / DSS 1.71.57 на HDD `D:`.
## Реализованный контракт
При групповом F5 Commander сначала последовательно копирует выбранные файлы,
а затем добавляет все выбранные каталоги текущего уровня как независимые
roots в одну traversal queue P15. Для каждого root действует тот же контракт:
- breadth-first обход и пустые каталоги;
- crash-safe temp/commit для файлов;
- cancel между файлами, каталогами и 4-КБ блоками;
- no-overwrite и self-target guard;
- один динамический EMM-блок очереди на всю группу;
- один refresh passive panel после операции.
Если top-level файл даёт ошибку или cancel до запуска обхода, directory roots
остаются `unprocessed`. При ошибке одного tree root операция останавливается;
уже committed-файлы и созданные каталоги сохраняются.
Это не меняет семантику `+`, `-`, `*`: групповые команды выделения по-прежнему
игнорируют каталоги. Каталог может попасть в F5-группу только после отдельного
`Insert`.
## Проверка
В active panel вручную выбраны `ADIR`, `BDIR` и `ROOT.BIN`; `KEEP.TXT`
оставлен невыбранным. Два root-каталога содержат по файлу, а `ADIR` — ещё и
пустой вложенный каталог. UI сообщил:
```text
Tree copied: 3 files; 3 dirs.
```
Внешний checker сравнил target с точным ожидаемым множеством и подтвердил
отсутствие `KEEP.TXT` и temp:
```text
PASS: selected file and two directory roots copied exactly.
```
Контрольные состояния:
- [два каталога и файл выбраны](../artifacts/mame-p16/group-tree/selected-roots.png);
- [группа скопирована](../artifacts/mame-p16/group-tree/copied.png);
- [passive panel перечитана](../artifacts/mame-p16/group-tree/passive-refreshed.png);
- [чистый возврат в DSS](../artifacts/mame-p16/group-tree/clean-dss-return.png).
## Компоновка
`_CODE` W2, DATA, BANK1 и размер EXE не изменились относительно P15. BANK2
вырос с 14547 до 14918 байт (+371); остаётся 1466 байт. Итоговая сборка:
```text
_CODE 9503; DATA 3524; heap 1821
BANK1 9687/16384; BANK2 14918/16384
SPRCMD.EXE 43080
```
Межбанковый аудит чист.
## Воспроизведение
```sh
make hdd-p16-group-tree-copy ALLOCS=3000
tests/run_mame_hdd.sh build/hdd/p16_group_tree_copy.chd \
tests/mame_p16_group_tree_copy.lua artifacts/mame-p16/local 60
tests/check_p16_group_tree_copy_hdd.sh build/hdd/p16_group_tree_copy.chd
```
@@ -0,0 +1,101 @@
# Sprinter Commander: рекурсивный group F8 0.2/P17
Дата: 10 сентября 2026 года.
Статус: `PASS` в MAME 0.288 / BIOS 3.06 / DSS 1.71.57 на HDD `D:`.
## Контракт
Если в панели есть выбранные записи, F8 показывает одно подтверждение для
всей группы. После подтверждения:
1. выбранные обычные файлы верхнего уровня удаляются в порядке панели;
2. выбранные каталоги становятся корнями одного BFS-job;
3. содержимое каталогов перечисляется через `F_FIRST/F_NEXT`, файлы удаляются
по мере обхода, а найденные подкаталоги добавляются в очередь;
4. после полного scan очередь проходится в обратном порядке, поэтому каждый
дочерний каталог удаляется раньше родителя;
5. исходный CWD восстанавливается, динамическая EMM-страница освобождается,
активная панель перечитывается.
Очередь та же, что у recursive F5: одна динамическая 16-КБ страница, 1008
узлов по 16 байт, максимальная глубина 32. В W2 дерево не хранится. `Esc`
проверяется между шагами; уже выполненные удаления при ошибке или отмене не
откатываются, что явно отражается в статусе.
При отсутствии группового выбора однообъектный F8 сохраняет прежний контракт:
файл удаляется, а каталог должен быть пустым.
## Проверка
Fixture содержит два выбранных дерева и выбранный файл, а также невыбранные
`KEEPDIR/SAFE.BIN` и `KEEP.TXT`. Первое дерево имеет пустой каталог, четыре
файла размеров 0/1/16/4097 и цепочку глубиной 12. Несколько файлов в одном
каталоге отдельно проверяют продолжение `F_NEXT` после `unlink` текущей
записи.
Сценарий сначала отменяет общее подтверждение и убеждается, что выбор и
панель не изменились, затем подтверждает удаление. После выхода содержимое
CHD извлекается и проверяется побайтно:
```text
PASS: selected file and two directory trees deleted; KEEP preserved.
```
Отдельный runtime-cancel fixture выбирает `TOP.BIN` и дерево из 180
подкаталогов. `TOP.BIN` удаляется до начала обхода, затем Lua подаёт `Esc` во
время заполнения очереди. После отмены дерево остаётся доступным, завершённое
удаление не откатывается, `KEEP.TXT` не меняется:
```text
PASS: runtime cancel kept remaining tree; completed TOP delete remains.
```
Чтобы `Esc` не ждал завершения длинной серии `F_NEXT`, delete-итератор
возвращает управление вызывающему коду после каждого добавленного каталога.
Контрольные кадры:
- [исходное дерево](../artifacts/mame-p17/tree-delete/before.png);
- [два каталога и файл выбраны](../artifacts/mame-p17/tree-delete/selected-file-and-trees.png);
- [общее подтверждение](../artifacts/mame-p17/tree-delete/group-confirm.png);
- [отмена до mutation сохранила группу](../artifacts/mame-p17/tree-delete/cancel-preserves.png);
- [после удаления остались только KEEP-объекты](../artifacts/mame-p17/tree-delete/deleted-keep-preserved.png);
- [чистый возврат в DSS](../artifacts/mame-p17/tree-delete/clean-dss-return.png).
Runtime-cancel:
- [выбранное дерево и верхнеуровневый файл](../artifacts/mame-p17/tree-delete-cancel/selected-before-cancel.png);
- [подтверждение](../artifacts/mame-p17/tree-delete-cancel/group-confirm.png);
- [частичный результат после Esc](../artifacts/mame-p17/tree-delete-cancel/runtime-cancelled.png);
- [чистый возврат в DSS](../artifacts/mame-p17/tree-delete-cancel/clean-dss-return.png).
## Компоновка
P17 остаётся в двух банках и не увеличивает образ EXE: каждый дополнительный
банк добавил бы 16 КБ независимо от фактического заполнения. Текущая сборка
`--memory big --safe --max-allocs 3000`:
```text
_CODE 9503; DATA 3524; heap 1821
BANK1 10562/16384; BANK2 15904/16384
SPRCMD.EXE 43080
```
В BANK2 остаётся 480 байт, поэтому следующий функциональный этап потребует
осмысленной перекладки модулей в BANK3. Межбанковый аудит чист.
## Воспроизведение
```sh
make hdd-p17-tree-delete ALLOCS=3000
tests/run_mame_hdd.sh build/hdd/p17_tree_delete.chd \
tests/mame_p17_tree_delete.lua artifacts/mame-p17/local 58
tests/check_p17_tree_delete_hdd.sh build/hdd/p17_tree_delete.chd
make hdd-p17-tree-delete-cancel ALLOCS=3000
tests/run_mame_hdd.sh build/hdd/p17_tree_delete_cancel.chd \
tests/mame_p17_tree_delete_cancel.lua artifacts/mame-p17/cancel-local 50
tests/check_p17_tree_delete_cancel_hdd.sh \
build/hdd/p17_tree_delete_cancel.chd
```
@@ -0,0 +1,89 @@
# Sprinter Commander: сохранение метаданных F5 0.2/P18
Дата: 10 сентября 2026 года.
Статус: `PASS` в MAME 0.288 / BIOS 3.06 / DSS 1.71.57 на HDD `D:`.
## Контракт
Одиночный, групповой и рекурсивный F5 сохраняют у каждого обычного файла:
- FAT date/time, включая двухсекундную точность;
- атрибуты readonly, hidden, system и archive.
Date/time задаются вызовом DSS `PUT_D_T` на открытом temp-файле после
последней записи и до `close()`. Атрибуты задаются через `ATTRIB` только
после атомарного rename temp в конечное имя. Такой порядок не делает temp
readonly до commit и сохраняет прежнюю защиту существующего target.
Если ошибка происходит до rename, общий cleanup закрывает дескрипторы и
удаляет temp. Если `ATTRIB` или восстановление CWD завершаются ошибкой уже
после rename, `ScCopyJob.committed` не позволяет выдавать результат за
отсутствующий файл: UI сообщает `File committed; finalization failed`.
Метаданные каталогов на этом этапе не переносятся.
## Проверка
Fixture создаёт `MSRC/META.BIN` размером 4097 байт с контролируемым payload,
датой `2001-02-03`, временем `04:05:06` и набором атрибутов `R/H/S/A`.
Подготовленный raw FAT проверяется до MAME, чтобы текущая дата сборки не могла
дать ложный положительный результат.
После F5 извлекается конечный CHD и собственным FAT16-reader проверяются обе
32-байтовые directory entries и цепочка кластеров:
```text
PASS: F5 preserved exact FAT date/time and R/H/S/A attributes.
```
На экране обе панели показывают одинаковые размер, `RHSA` и
`2001-02-03 04:05`; секунды и payload подтверждает raw-проверка.
Контрольные кадры:
- [исходные метаданные](../artifacts/mame-p18/metadata/source-metadata.png);
- [файл скопирован с теми же полями](../artifacts/mame-p18/metadata/copied-metadata.png);
- [пассивная панель после переключения](../artifacts/mame-p18/metadata/passive-metadata.png);
- [чистый возврат в DSS](../artifacts/mame-p18/metadata/clean-dss-return.png).
После P18 повторно пройдены:
- P5 copy-fault matrix: `ALL PASS`, CHD-check `PASS`;
- P16 recursive group F5: UI корректен, все выбранные деревья и файлы
совпали побайтно.
## ABI DSS
`sc_metadata_set_datetime()` раскрывает packed FAT date/time в поля
`PUT_D_T $18`. Низкоуровневый leaf получает handle в `A`, near-указатель на
семибайтовую структуру в `DE`, загружает год в `IX` и сохраняет caller IX.
Для `ATTRIB $16` сгенерированный `.asm` подтвердил важную особенность
`__sdcccall(1)`: второй однобайтовый аргумент banked-wrapper лежит на стеке,
а `E` занят номером банка. Поэтому публичная banked-функция вызывает
локальный raw-leaf с обычным ABI; leaf снимает однобайтовый аргумент и адрес
возврата явно. Оба пути переводят `CF/A` в `errno`.
## Компоновка
Сборка `--memory big --safe --max-allocs 3000` остаётся двухбанковой и не
увеличивает EXE за счёт пустого третьего банка:
```text
_CODE 9503; DATA 3531; heap 1814
BANK1 11011/16384; BANK2 16145/16384
SPRCMD.EXE 43080
```
В BANK2 остаётся 239 байт. Следующий заметный UI/policy-модуль следует
размещать с осмысленной перекладкой в BANK3. Межбанковый аудит чист.
## Воспроизведение
```sh
make hdd-p18-metadata ALLOCS=3000
tests/run_mame_hdd.sh build/hdd/p18_metadata.chd \
tests/mame_p18_metadata.lua artifacts/mame-p18/local 48
tests/check_p18_metadata_hdd.sh build/hdd/p18_metadata.chd
```
@@ -0,0 +1,129 @@
# Sprinter Commander: conflict-policy F5 0.2/P19
Дата: 10 сентября 2026 года.
Статус: `PASS` в MAME 0.288 / BIOS 3.06 / DSS 1.71.57 на HDD `D:`.
## Поведение
Если конечное имя файла существует, F5 показывает единый choice-dialog:
- `O` — безопасно заменить только этот файл;
- `S` — пропустить только этот файл;
- `R` — ввести другое DOS 8.3-имя;
- `A` — заменить этот и все следующие конфликтующие файлы текущей группы;
- `N` — пропустить этот и все следующие конфликты текущей группы;
- `Esc` — отменить текущую операцию.
Policy `all` живёт только до завершения одного вызова F5 и распространяется
на top-level группу и файлы рекурсивной job. Readonly target не заменяется
молча даже после ранее выбранного `overwrite all`: для него снова появляется
диалог `Read-only target`. Групповые `+/-/*` по-прежнему работают только над
файлами; выбор каталогов через `Insert` не менялся.
`Rename` меняет только имя target, проверяет один компонент DOS 8.3 и не
переименовывает source. Если новое имя тоже занято, policy запрашивается
повторно.
## Транзакционная замена
Существующий target не удаляется перед копированием. После полной записи и
закрытия нового temp выполняется последовательность:
1. выбирается отсутствующее имя `~SBxxxx.BAK` в том же каталоге;
2. readonly временно снимается только после явного разрешения overwrite;
3. старый target переименовывается в backup;
4. новый temp переименовывается в target;
5. новому target назначаются исходные date/time и R/H/S/A;
6. backup удаляется.
Если шаг commit нового temp не проходит, старый target переименовывается
обратно и его атрибуты восстанавливаются. Заранее существующие backup-имена
пропускаются, а не перезаписываются. Ошибка после необратимого commit
помечается `ScCopyJob.committed`, поэтому UI не сообщает, что target якобы
отсутствует.
## Проверка
Отдельная target-проба прошла на реальном DSS-пути:
```text
PASS default still refuses existing target
PASS overwrite commits through backup
PASS explicit readonly overwrite
PASS pre-existing backup name skipped
PASS replace policy accepts vanished target
PASS distinct destination name
PASS rename failure rolls original target back
PASS EMM restored
ALL PASS
```
CHD-check побайтно подтвердил заменённые файлы, rollback исходного payload,
сохранность заранее созданного `~SB0000.BAK` и отсутствие новых backup/temp:
```text
PASS: transactional overwrite, readonly, collision and rollback.
```
Полный UI-сценарий выбрал три файла для `overwrite all`, получил отдельный
readonly prompt, затем применил `skip all` к двум файлам, скопировал
`REN.BIN` как `NEXT.BIN` и отменил конфликт `CANCEL.BIN`. Итоговая проверка:
```text
PASS: overwrite-all, readonly prompt, skip-all, rename and cancel.
```
Контрольные кадры target-пробы:
- [ALL PASS](../artifacts/mame-p19/overwrite-core/all-pass.png);
- [чистый возврат](../artifacts/mame-p19/overwrite-core/clean-dss-return.png).
UI policy:
- [выбор overwrite all](../artifacts/mame-p19/policy/overwrite-choice.png);
- [повторный readonly prompt](../artifacts/mame-p19/policy/readonly-reprompt.png);
- [три файла заменены](../artifacts/mame-p19/policy/overwrite-complete.png);
- [выбор skip all](../artifacts/mame-p19/policy/skip-all-choice.png);
- [оба файла пропущены](../artifacts/mame-p19/policy/skip-complete.png);
- [rename-conflict](../artifacts/mame-p19/policy/rename-choice.png);
- [`NEXT.BIN` создан](../artifacts/mame-p19/policy/renamed.png);
- [отмена конфликта](../artifacts/mame-p19/policy/cancel-choice.png);
- [диалог полностью убран после Esc](../artifacts/mame-p19/policy/cancel-cleared.png);
- [итог пассивной панели](../artifacts/mame-p19/policy/target-result.png);
- [чистый возврат в DSS](../artifacts/mame-p19/policy/clean-dss-return.png).
В процессе визуальная проверка нашла и устранила stale-dialog: после
одиночных conflict `error/cancel/skip` теперь выполняется полный redraw, а не
обновление только двух нижних строк.
После итоговой правки повторно пройдены P5 copy-fault matrix и P18 exact FAT
metadata.
## Компоновка
EXEC-модуль перенесён из почти полного BANK2 в BANK1. Его строковые статусы
теперь всегда копируются в W2 BSS, поэтому наружу не уходит указатель на
bank-local literal. Это позволило оставить приложение двухбанковым:
```text
_CODE 9503; DATA 3561; heap 1784
BANK1 14374/16384; BANK2 15665/16384
SPRCMD.EXE 43080
```
Межбанковый аудит чист. В BANK1 остаётся 2010 байт, в BANK2 — 719.
## Воспроизведение
```sh
make hdd-p19-overwrite ALLOCS=3000
tests/run_mame_hdd.sh build/hdd/p19_overwrite.chd \
tests/mame_p19_overwrite.lua artifacts/mame-p19/core-local 26
tests/check_p19_overwrite_hdd.sh build/hdd/p19_overwrite.chd
make hdd-p19-policy ALLOCS=3000
tests/run_mame_hdd.sh build/hdd/p19_policy.chd \
tests/mame_p19_policy.lua artifacts/mame-p19/policy-local 106
tests/check_p19_policy_hdd.sh build/hdd/p19_policy.chd
```
@@ -0,0 +1,81 @@
# Sprinter Commander: результаты палитровой пробы P2.2
Дата: 6 сентября 2026 года.
Статус: `PASS` в MAME; проверка на реальном Sprinter ожидается.
Окружение: MAME 0.287 (`b0c4527c`), машина `sprinter`, BIOS `v3.06`.
Исходник пробы находится в `tests/p2_palette/p2palette.c`, кадры — в
[`artifacts/mame-p2/palette/`](../artifacts/mame-p2/palette/).
## 1. Проверенный контракт
Проба использует только публичные системные функции
`text_pal_get_color()`/`text_pal_set_color()`:
- сохраняет шесть применяемых атрибутов во всех четырёх текстовых планах;
- задаёт normal-paper и flash-paper одинаковыми;
- задаёт normal-ink и flash-ink одинаковыми;
- читает обратно все 24 записи и сравнивает каждый RGB-компонент;
- показывает одновременно шесть контрастных цветовых сочетаний;
- перед выходом восстанавливает каждую сохранённую запись.
Проверенные сочетания: white/blue, yellow/green, white/red,
white/magenta, black/lightgray и black/cyan. Runtime-результат:
```text
Readback of 6 attrs x 4 planes: PASS (0 mismatches)
```
## 2. Стабильность изображения
Пять кадров одной неизменной сцены сняты в разных моментах. После
декодирования PNG область экрана Sprinter во всех кадрах пиксельно идентична.
Единственное различие между первым и остальными кадрами — 42 пикселя в
прямоугольнике `x=0..13, y=42..44`: это внешний индикатор обращения к
`Drive A` в layout MAME, а не видеовыход Sprinter. Его состояние в экранных
тестах не учитывается.
Файлы `phase-b.png``phase-e.png` идентичны целиком. Их SHA-256:
```text
5eda33a6289dcd0afbb10573b70bcd701c428d6ed8f2aaae55d16c8d32dbe9cb
```
Артефакты:
- [phase-a.png](../artifacts/mame-p2/palette/phase-a.png) — горит индикатор
Drive A;
- [phase-b.png](../artifacts/mame-p2/palette/phase-b.png),
[phase-c.png](../artifacts/mame-p2/palette/phase-c.png),
[phase-d.png](../artifacts/mame-p2/palette/phase-d.png),
[phase-e.png](../artifacts/mame-p2/palette/phase-e.png) — стабильная сцена;
- [restored-dos.png](../artifacts/mame-p2/palette/restored-dos.png) — возврат
в DSS после восстановления палитры.
## 3. Исправление прежней интерпретации
Ранее встроенный предпросмотр двух PNG ошибочно показал исчезновение части
цветных строк. Проверка исходных файлов опровергла это:
- `partial-flash-phase.png` и `partial-contrast-stable.png` из P1 имеют
идентичные декодированные RGB-пиксели;
- все три исторически названных `flash-before-*.png` из P2.1 также полностью
идентичны;
- текущая P2.2-сцена различается только внешним LED дисковода MAME.
Следовательно, гипотеза о нестабильных FLASH-планах была артефактом
предпросмотра. Она не является ограничением BIOS, libc, MAME или Commander.
## 4. Принятое решение
Многоцветная тема разрешена для Commander. Модуль `sc_theme`:
- владеет точными RGB смысловых атрибутов;
- сохраняет только затрагиваемые записи всех четырёх планов;
- устанавливает одинаковые normal/FLASH-пары;
- восстанавливает исходные записи при любом штатном cleanup.
До аппаратного прогона `PASS` относится к MAME 0.287 / BIOS 3.06. На реальном
Sprinter требуется проверить те же цвета и возврат исходной палитры после
выхода.
@@ -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.
@@ -0,0 +1,81 @@
# Sprinter Commander: результаты экранной пробы P2.1
Дата: 6 сентября 2026 года.
Статус: `PASS` в MAME; проверка на реальном Sprinter ожидается.
Окружение: MAME 0.287 (`b0c4527c`), машина `sprinter`, BIOS `v3.06`.
Исходники пробы находятся в `tests/p2_screen/`, сохранённые кадры — в
[`artifacts/mame-p2/`](../artifacts/mame-p2/README.md).
## 1. Проверяемая граница
`p2screen.exe` проверяет системный координатный backend, а не интерфейс
готового Commander:
- экранная модель 80x32 хранится в EMM как пары `(char, attr)`;
- прямоугольники выводятся ESTEX `WINREST`;
- фактические клетки читаются ESTEX `RDCHAR`;
- области панелей сдвигаются ESTEX `SCROLL`;
- BIOS window descriptors не открываются;
- к `RGADR`, `RGMOD` и VRAM-банкам программа не обращается;
- палитра в принятом варианте теста не читается и не изменяется.
Сборка: `--memory big --safe`.
| Область | Значение |
|---|---:|
| `_CODE` | 4 458 байт (`0x8100..0x926A`) |
| данные | 581 байт (конец `0x94AF`) |
| EXE | 5 004 байта |
| heap после статики | 9 809 байт |
| стек | 1 279 байт |
## 2. Runtime-результаты
| Этап | Результат | Артефакт |
|---|---|---|
| Полный `WINREST` 80x32 и полная сверка `RDCHAR` | PASS | [full.png](../artifacts/mame-p2/screen/full.png) |
| Частичный `WINREST` 20x2 из offset `0x2000` | PASS | [partial-offset.png](../artifacts/mame-p2/screen/partial-offset.png) |
| Scroll внутренности левой панели вверх | PASS | [left-scroll-up.png](../artifacts/mame-p2/screen/left-scroll-up.png) |
| Scroll внутренности правой панели вниз | PASS | [right-scroll-down.png](../artifacts/mame-p2/screen/right-scroll-down.png) |
| 1000 scroll, синхронная EMM-модель, итоговая сверка `RDCHAR` и W3 | PASS | [stress-final.png](../artifacts/mame-p2/screen/stress-final.png) |
| Финальная сцена в трёх временных точках | PASS, файлы идентичны | [phase A](../artifacts/mame-p2/screen/stable-phase-a.png), [B](../artifacts/mame-p2/screen/stable-phase-b.png), [C](../artifacts/mame-p2/screen/stable-phase-c.png) |
SHA-256 каждого из трёх финальных кадров:
```text
dabeb114f8a7574462aeeb0731363608c1afa940da66d518f734335b36fe8f3b
```
Полная сверка после каждого функционального этапа сравнивала все 2560 клеток
экрана с EMM-моделью, включая рамки, пассивную панель и общие строки. Поэтому
`PASS` scroll означает не только появление новой строки, но и отсутствие
побочного сдвига соседней области.
## 3. Граница палитры и исправление диагностики
P2.1 намеренно принят с одним системным атрибутом `0x30` и не вызывает
палитровые функции: так координатный backend проверяется отдельно от темы.
Первичная визуальная интерпретация диагностических PNG ошибочно сообщала, что
в одной временной фазе часть цветных символов исчезает. Проверка самих файлов
показала обратное: три `flash-before-*.png` имеют одинаковые SHA-256 и
одинаковые декодированные RGB-пиксели. Два именованных цветных кадра P1 также
пиксельно идентичны.
Отдельная проба P2.2 затем подтвердила save/set/readback/restore всех четырёх
планов и стабильную сцену с шестью атрибутами. Подробности и исправление
ошибочной гипотезы приведены в
[результатах P2.2](p2-palette-results.md).
## 4. Что ещё не проверено
- повторная инициализация backend после child, сменившего видеорежим;
- измерение времени полного present, одной строки и системного scroll;
- тот же сценарий на реальном Sprinter;
- интеграционный прогон многоцветного `SPRCMD.EXE` после подключения темы.
Эти пункты не блокируют реализацию экранной модели и skeleton P2. Аппаратный
прогон остаётся gate перед признанием backend полностью платформенно
подтверждённым. Многоцветная тема уже прошла отдельный MAME-gate P2.2.
@@ -0,0 +1,98 @@
# Sprinter Commander: результаты двухпанельного skeleton P2
Дата: 6 сентября 2026 года.
Статус: многоцветный skeleton `PASS` в MAME; файловая система и аппаратная
проверка ожидаются.
Связанные документы:
[архитектура](commander-architecture.md),
[roadmap](commander-roadmap.md),
[экранный backend](p2-screen-backend.md),
[результаты P2.1](p2-screen-probe-results.md)
## 1. Реализованная граница
`build/sprcmd.exe` — первая запускаемая форма Commander, а не очередная
платформенная проба. В ней уже разделены:
- `ScApp`, инициализация, главный цикл и идемпотентное состояние cleanup;
- три EMM-страницы: левая панель, правая панель и screen model;
- `sc_screen` — единственный владелец модели 80x32 `(char, attr)`;
- `sc_video_system``WINREST` и прямоугольный `SCROLL` без BIOS-окон;
- renderer двух панелей, status и строки функциональных клавиш;
- нормализованные `ScKeyEvent` и `ScCommand`;
- навигация по синтетическим P2-записям, включая системный scroll;
- единая модальная поверхность для сообщений и подтверждения выхода.
Синтетические строки нужны только для проверки cursor/top и scroll. Они не
являются подменой каталога: `ffirst/fnext` skeleton не вызывает, настоящий
источник файлов добавляется в P3.
## 2. Сборка
Команда: `make`, конфигурация `--memory big --safe`.
| Область | Значение |
|---|---:|
| `_CODE` | 7 129 байт (`0x8100..0x9CD9`) |
| данные | 443 байта (конец `0x9E94`) |
| EXE | 7 675 байт |
| heap после статики | 7 276 байт |
| стек | 1 279 байт |
Банков кода skeleton пока не требует. Контракты уже рассчитаны на `big`, а
холодные функции будут переноситься в W1 по мере появления P3/P4-кода.
## 3. MAME-сценарий
Окружение: MAME 0.287 (`b0c4527c`), машина `sprinter`, BIOS `v3.06`.
Сценарий воспроизводится файлом `tests/mame_p2_skeleton.lua`.
| Действие | Наблюдение | Результат |
|---|---|---|
| Первый экран | две многоцветные панели 80x32, левая `[ACTIVE]`, cursor/top `0/0` | [PASS](../artifacts/mame-p2/skeleton-color/initial-left-active.png) |
| `Tab` | `[ACTIVE]` и `>` перешли на правую панель | [PASS](../artifacts/mame-p2/skeleton-color/tab-right-active.png) |
| `PgDn`, `Down` | правая панель показывает строки 002028, cursor/top `28/2`; сосед и рамки неподвижны | [PASS](../artifacts/mame-p2/skeleton-color/navigation-scroll.png) |
| `F10` | красный диалог подтверждения поверх модели | [PASS](../artifacts/mame-p2/skeleton-color/quit-dialog.png) |
| `N` | обе панели и тема полностью восстановлены из EMM | [PASS](../artifacts/mame-p2/skeleton-color/quit-cancelled-restored.png) |
| `F1`, `Esc` | общий message dialog открылся и восстановил экран | [PASS](../artifacts/mame-p2/skeleton-color/reserved-command-dialog.png), [после закрытия](../artifacts/mame-p2/skeleton-color/message-closed-restored.png) |
| повторный `F10`, `Enter` | палитра восстановлена, штатный выход в DSS | [PASS](../artifacts/mame-p2/skeleton-color/clean-exit-dos.png) |
| второй запуск после выхода | приложение снова инициализировало EMM и тему | [PASS](../artifacts/mame-p2/skeleton-color/repeat-second-launch.png) |
| второй cleanup | возврат в DSS без сообщения об утечке EMM | [PASS](../artifacts/mame-p2/skeleton-color/repeat-second-clean-exit.png) |
## 4. Проверка ресурсов
В начале `sc_app_init()` сохраняет `mem_info.total/free`, затем выделяет один
блок из трёх страниц. `sc_app_cleanup()` освобождает блок, повторяет
`mem_info()` и возвращает ошибку, если оба значения не восстановились. При
ошибке основной модуль печатает отдельное сообщение и возвращает код 4.
В MAME этого сообщения не было ни после первого, ни после второго процесса.
Второй процесс успешно выделил тот же объём и полностью отрисовался. Таким
образом, проверка включает и внутреннее сравнение счётчиков, и повторный
запуск с чистого состояния.
## 5. Экран и палитра
Skeleton не открывает BIOS window descriptors и не обращается к VRAM
напрямую. Панели и диалоги — прямоугольники в собственной EMM-модели.
Системные BIOS/ESTEX-вызовы палитры **не запрещены**. Первый зафиксированный
skeleton намеренно отображал все semantic attributes системным `0x30`, чтобы
изолировать проверку приложения. После успешной отдельной пробы P2.2 добавлен
`sc_theme`: разные смысловые атрибуты, save/set всех четырёх планов и restore
при cleanup. Renderer и формат экранной модели при этом не изменились.
Интеграционный MAME-сценарий прошёл с белыми/жёлтыми/светло-голубыми
элементами на синем фоне, чёрным на cyan/lightgray и белыми красными
диалогами. Второй запуск подтвердил повторную инициализацию темы после её
восстановления первым процессом.
## 6. Следующие работы
- повторить палитровую пробу P2.2 на реальном Sprinter;
- повторная инициализация `sc_video` после child, сменившего видеорежим;
- измерение full/row/scroll present;
- аппаратный прогон P2.1 и skeleton;
- P3: заменить синтетические строки настоящими независимыми каталогами.
@@ -0,0 +1,146 @@
# Sprinter Commander: результаты файловых панелей P3
Дата: 7 сентября 2026 года.
Статус: `PASS` в MAME для навигации, независимых панелей, EMM-store на
границе 640/641, сортировки и явной обработки предела каталога DSS 1.71.
Связанные документы:
[требования](commander-requirements.md),
[архитектура](commander-architecture.md),
[roadmap](commander-roadmap.md),
[большие каталоги DSS](dss-large-directories.md)
## 1. Проверенная граница
В P3 синтетические записи заменены настоящим источником `F_FIRST/F_NEXT`.
Каждая панель имеет собственные путь, cursor/top и физическую EMM-страницу.
Запись панели занимает 24 байта, поэтому одна 16-КБ страница вмещает
жёсткий продуктовый предел 640 записей и оставляет 1024 байта запаса.
Реализованы и проверены:
- шаблон перечисления `*.*` и имена DOS 8.3;
- синтетическая `..` в некорневом каталоге без дублей `.`/`..` от DSS;
- каталоги перед файлами и сортировка имён без учёта регистра ASCII;
- вход в каталог, возврат через `..`/`Ctrl+PgUp`, refresh и независимость
пассивной панели;
- навигация `Up/Down/PgUp/PgDn/Home/End` без повторного scan;
- частичный результат при ошибке DSS 35 и отдельная виртуальная запись
`>>> MORE...`.
## 2. Сборка
Основная команда: `make`.
Конфигурация: `--memory big --safe --max-allocs 3000`.
| Область | Значение |
|---|---:|
| `_CODE` | 7728 байт (`0x8100..0x9F30`) |
| данные | 2281 байт (конец `0xA819`) |
| свободная куча W2 | 4839 байт |
| стек | 1279 байт |
| `_BANK1` — scan/sort | 2299 / 16384 байт (14,0%) |
| `_BANK2` — renderer | 2902 / 16384 байт (17,7%) |
| EXE | 41305 байт |
`check_bank_calls.py` не нашёл прямых вызовов в чужой банк или публикации
указателей на данные банка.
## 3. Прямая диагностика DSS
Образ содержит `BIGDIR` из 680 обычных файлов. Исправленная проба `P3DIR`
получила:
```text
count=512
errno=35
511=B0000598.DAT
512=B0000599.DAT
513=
```
Это подтверждает границу стабильного DSS и отсутствие продолжения за ней.
Конкретные последние имена отражают физический порядок записей, созданный
`mcopy`, и сами по себе не являются контрактом теста.
Артефакт: [экран P3DIR](../artifacts/mame-p3/panels/dss-enum-limit.png).
## 4. Диагностика общего пути Commander
Проба `P3SCAN` использует те же `sc_source_fs`, `sc_store` и `sc_sort`, что
основная программа. Получен результат:
```text
scan: rc=0 errno=35 count=512 trunc=1
first: .. attr=10 flags=01
last : >>> MORE... attr=00 flags=04
```
Ошибка 35 сознательно остаётся в `errno` как причина усечения, но scan
возвращает успешный частичный снимок. Красный маркер занимает последний
элемент снимка и сортируется после файлов.
Артефакт: [экран P3SCAN](../artifacts/mame-p3/panels/scan-marker.png).
## 5. Интеграционный сценарий Commander
Исторические артефакты ниже сняты в MAME 0.287 с FDD `A:`. Текущий
регрессионный сценарий предельного каталога переведён на HDD `D:`:
`make hdd-stress` создаёт `build/hdd/p3_stress.chd`, а
`tests/mame_p3_stress.lua` сначала переключает DSS на `D:`. FDD-сборщик
`tests/make_p3_stress_floppy.sh` оставлен только для smoke-совместимости.
| Действие | Подтверждённое наблюдение | Результат |
|---|---|---|
| Корень | обе панели независимо открыли один реальный каталог | [PASS](../artifacts/mame-p3/panels/root-both-panels.png) |
| Вход и `..` | левая панель вошла в `TESTDIR` и вернулась в корень | [PASS](../artifacts/mame-p3/panels/left-testdir-parent.png), [возврат](../artifacts/mame-p3/panels/enter-parent-return.png) |
| `Ctrl+R` | после refresh сохранён выбор `INNER.TXT` | [PASS](../artifacts/mame-p3/panels/refresh-preserves-inner.png) |
| `Tab` и навигация справа | пути и cursor/top панелей не смешиваются | [PASS](../artifacts/mame-p3/panels/independent-panel-state.png) |
| `Ctrl+PgUp` слева | изменена только активная левая панель | [PASS](../artifacts/mame-p3/panels/ctrl-pgup-left-only.png) |
| Вход в `EMPTY` | единственная запись — синтетическая `..`, count=1 | [PASS](../artifacts/mame-p3/panels/empty-parent.png) |
| Вход в `MANY`, `PgDn`, `Down` | cursor/top меняются до 28/2 без rescan, рамки и соседняя панель сохраняются | [PASS](../artifacts/mame-p3/panels/navigation-scroll.png) |
| Вход в `BIGDIR` | показан частичный снимок, count=512 и признак `entries+` | [PASS](../artifacts/mame-p3/panels/bigdir-partial.png) |
| `End` | выбран последний красный `>>> MORE...`, info показывает `<DSS LIMIT>` | [PASS](../artifacts/mame-p3/panels/dss-marker-selected.png) |
| `Enter` на маркере | выведено сообщение о недоступном остатке DSS; переход или EXEC не выполнен | [PASS](../artifacts/mame-p3/panels/dss-marker-message.png) |
| `F10` | открыт диалог выхода поверх текущего состояния | [PASS](../artifacts/mame-p3/panels/dss-limit-quit-dialog.png) |
| `Enter` в диалоге | выполнен cleanup и возврат в `A:\>` | [PASS](../artifacts/mame-p3/panels/dss-limit-clean-exit.png) |
Скриншот фиксирует только состояние изображения в конкретный момент и не
используется в одиночку для вывода о причине возможного дефекта. Подозрение
на ошибку должно дополнительно подтверждаться соседними кадрами, ожидаемым
состоянием сценария и, при необходимости, отдельной диагностической пробой.
## 6. Синтетическая граница EMM-store
Проба `tests/p3_store_probe/p3store.c` не вызывает DSS и поэтому достигает
продуктового предела, который нельзя получить из реального каталога DSS
1.71. Сборка выполнена с `--max-allocs 3000`.
```text
entry=24 capacity=640 bytes=15360 reserve=1024
write: count=640 first=1 middle=1 last=1
guard: put640=1 get640=1 tail=1
cleanup: free_after=222 restored=1
PASS: store boundary is intact.
```
Проверены записи 0, 319 и 639. Запись и чтение индекса 640 отклонены,
байты в начале и конце 1024-байтового резерва не изменились, EMM-страница
освобождена без утечки.
Артефакт:
[граница 640/641](../artifacts/mame-p3/panels/store-boundary-640-641.png).
## 7. Итог и следующая граница
Функциональный gate файловых панелей P3 закрыт в MAME. Аппаратный прогон на
реальном Sprinter ещё нужен, но не блокирует начало P4.
Следующая работа PoC:
1. копирование одного файла через отдельную EMM-страницу в W3;
2. безопасный временный файл, обработка ошибок и cleanup;
3. запуск `.EXE` через ESTEX с восстановлением экрана после child;
4. регрессионный MAME-сценарий для копирования, файлов больше 64 КБ и EXEC.
@@ -0,0 +1,98 @@
# Sprinter Commander: результаты копирования P4.1
Дата: 7 сентября 2026 года.
Статус: `PASS` в MAME 0.288 / BIOS 3.06 / DSS 1.71.57 на HDD `D:` для
однофайлового F5, прогресса, отмены, запрета overwrite и нулевого файла.
Связанные документы:
[требования](commander-requirements.md),
[архитектура](commander-architecture.md),
[roadmap](commander-roadmap.md),
[носители тестов](test-media.md).
## Реализация
- F5 берёт обычный файл активной панели и каталог пассивной панели;
- каталоги и виртуальный маркер DSS явно отклоняются;
- source и уникальный temp открыты одновременно — не более двух fd;
- блок 4096 байт находится только в четвёртой EMM-странице `ScApp` и
временно отображается в W3 функциями `bank_read_page()` и
`bank_write_page()`;
- счётчики размера/позиции 32-битные;
- temp создаётся через `O_CREAT|O_EXCL`, на ошибке или Esc закрывается и
удаляется;
- перед commit конечное имя проверяется повторно;
- commit вызывает DSS `RENAME` двумя basename в каталоге пассивной панели и
восстанавливает глобальный CWD;
- после успеха пассивная панель перечитывается.
UI-оркестрация и форматирование прогресса вынесены из резидентного W2 в
`sc_copy_ui.c` (BANK2). Полноразмерного статического буфера нет ни в W2, ни в
банке кода.
## Найденная особенность DSS RENAME
Первый HDD-прогон перенёс все 131109 байт во временный файл, но завершился
`errno 16` (`EINAME`), а валидатор CHD подтвердил отсутствие результата.
Причина установлена не по одиночному кадру, а по сочетанию:
1. позднего прогресса `122880/131109`;
2. отдельного итогового кадра с `errno 16`;
3. извлечённого состояния HDD;
4. исходника `DSS/API/Rename.asm`: операция переименовывает записи текущего
каталога, а полный новый путь не является допустимым новым именем.
Исправление ограничено commit-фазой: job сохраняет CWD, временно входит в
target-dir, передаёт два коротких имени и восстанавливает CWD на всех обычных
путях возврата.
## Матрица MAME
Образ создаёт `tests/make_p4_copy_hdd.sh`, сценарий выполняет
`tests/mame_p4_copy_hdd.lua`, итог проверяет `tests/check_p4_copy_hdd.sh`.
| Сценарий | Результат |
|---|---|
| `LARGE.BIN`, 131109 байт | скопирован, passive refresh показывает файл; содержимое побайтно совпало |
| `EXIST.TXT` | F5 отказал до изменения существующего целевого файла |
| `CANCEL.BIN`, 4194341 байт | виден промежуточный прогресс; Esc удалил temp и не создал target |
| `EMPTY.ZRO`, 0 байт | создан корректный пустой файл и показан passive refresh |
| cleanup/exit | временных `~SC*.TMP` нет; возврат в `D:\>` |
Артефакты:
- [успешный файл больше 64 КБ](../artifacts/mame-p4/copy/hdd-copy-success.png);
- [отказ overwrite](../artifacts/mame-p4/copy/hdd-existing-refused.png);
- [прогресс до Esc](../artifacts/mame-p4/copy/hdd-cancel-progress.png);
- [состояние после отмены](../artifacts/mame-p4/copy/hdd-cancel-cleanup.png);
- [успешный нулевой файл](../artifacts/mame-p4/copy/hdd-zero-copy-success.png);
- [чистый выход на HDD](../artifacts/mame-p4/copy/hdd-clean-exit.png);
- [низкоуровневый read/write EMM-page probe](../artifacts/mame-p4/copy/bank-page-io.png).
Финальный файловый валидатор сообщил:
```text
PASS: LARGE/EMPTY скопированы; EXIST сохранён; cancel/temp отсутствуют.
```
## Размер сборки
Конфигурация: `--memory big --safe --max-allocs 3000`.
| Область | Размер |
|---|---:|
| `_CODE` W2 | 8795 байт |
| данные W2 | 3161 байт |
| свободная куча W2 | 2892 байта |
| стек | 1279 байт |
| BANK1: scan/sort/copy job | 4327 / 16384 байт |
| BANK2: draw/copy UI | 4411 / 16384 байт |
| `SPRCMD.EXE` | 42372 байта |
`check_bank_calls.py` — чисто. Общий `make size-check` — 67 программ без
роста; отдельно отмечается уже существующий новый тест `hello3`, не связанный
с Commander.
P4.1 закрыт для требований PoC `REQ-COPY-01..12`; запуск `.EXE` и вся
стабилизация PoC продолжены в отчётах P4.2 и P5.
+100
View File
@@ -0,0 +1,100 @@
# Sprinter Commander: результаты запуска EXE P4.2
Дата: 8 сентября 2026 года.
Статус: `PASS` в MAME 0.288 / BIOS 3.06 / DSS 1.71.57 на HDD `D:`.
Выполнены требования `REQ-EXEC-01..06`: запуск по `Enter`, возврат в
Commander после изменившей окружение программы, обработка плохого EXE и отказ
для неподдерживаемого типа файла.
Связанные документы:
[требования](commander-requirements.md),
[архитектура](commander-architecture.md),
[roadmap](commander-roadmap.md),
[носители тестов](test-media.md).
## Реализация
- `Enter` запускает только обычную запись с расширением `.EXE` без учёта
регистра; каталог, маркер предела DSS и иной тип файла не передаются EXEC;
- полный путь строится в резидентной памяти, ESTEX `EXEC $40` вызывается с
`B=1` и синхронно возвращает код завершения в диапазоне `00..FF`;
- перед запуском сохраняются текущий диск и 256-байтный CWD;
- child получает каталог активной панели как CWD, что позволяет программам
без аргументов открывать соседние файлы; затем CWD Commander возвращается;
- одновременный copy job запрещён; пути временно используют уже существующие
строковые поля `ScCopyJob`, поэтому EXEC не добавил два новых буфера в W2;
- после child восстанавливаются диск/CWD, режим 80x32 и восемь записей
палитры Commander во всех четырёх planes;
- повторная установка темы не перезаписывает снимок исходной системной
палитры, который нужен штатному cleanup;
- обе панели перечитываются, затем экран полностью строится из EMM-модели;
- успешный статус показывает код завершения, ошибка загрузчика — имя и
`errno`; Commander остаётся управляемым.
Низкоуровневая обёртка находится в `sc_platform_exec.c`, а холодная
оркестрация и форматирование статуса — в `sc_exec_ui.c` (BANK2).
## Интеграционный child
`tests/p4_exec_child/p4child.exe` намеренно создаёт неблагоприятное для
родителя состояние:
1. создаёт `EXECLEFT/CREATED.TXT` и `EXECRGHT/PASSIVE.TXT`;
2. оставляет CWD в `EXECLEFT`;
3. переключает экран в 40x32;
4. заменяет используемые Commander цвета ярко-зелёной/пурпурной палитрой;
5. ждёт `Enter` и возвращает код `5A`.
Имена каталогов теста строго соответствуют 8.3. Промежуточный вариант
`EXECRIGHT` оказался VFAT long name и был виден DSS как `EXECRI~1`; отказ
`chdir("EXECRIGHT")` был ошибкой fixture, а не доказательством дефекта DSS.
## Матрица MAME
Образ создаёт `tests/make_p4_exec_hdd.sh`, сценарий выполняет
`tests/mame_p4_exec_hdd.lua`, итоговое содержимое проверяет
`tests/check_p4_exec_hdd.sh`.
| Сценарий | Результат |
|---|---|
| `P4CHILD.EXE` | child показал `active=0 passive=0` и CWD `\\EXECLEFT` |
| возврат из child | Commander снова в 80x32 с исходной темой; статус содержит `5A` |
| refresh активной панели | появился `CREATED.TXT`, размер 19 |
| refresh пассивной панели | появился `PASSIVE.TXT`, размер 27 |
| `BROKEN.EXE` | показано `Cannot execute BROKEN.EXE (errno 17)`; приложение продолжило работу |
| `NOTE.TXT` | показано имя объекта и явный отказ: поддерживаются только `.EXE` |
| cleanup/exit | диалог работает; системная палитра восстановлена; возврат в `D:\>` |
После остановки MAME CHD был извлечён и оба marker-файла сравнены побайтно:
```text
PASS: child создал active/passive markers с ожидаемым содержимым.
```
Артефакты:
- [child после изменения режима, палитры и CWD](../artifacts/mame-p4/exec/child-mode-palette-cwd.png);
- [Commander после восстановления и refresh обеих панелей](../artifacts/mame-p4/exec/commander-restored-both-panels.png);
- [ошибка повреждённого EXE](../artifacts/mame-p4/exec/broken-exe-error.png);
- [отказ для обычного TXT](../artifacts/mame-p4/exec/unsupported-file.png);
- [диалог выхода после EXEC](../artifacts/mame-p4/exec/quit-after-exec.png);
- [чистый возврат в DSS](../artifacts/mame-p4/exec/clean-dss-return.png).
## Размер сборки
Конфигурация: `--memory big --safe --max-allocs 3000`.
| Область | Размер |
|---|---:|
| `_CODE` W2 | 8870 байт |
| данные W2 | 3240 байт |
| свободная куча W2 | 2738 байт |
| стек | 1279 байт |
| BANK1: scan/sort/copy job | 4242 / 16384 байт |
| BANK2: draw/copy/EXEC UI | 5973 / 16384 байт |
| `SPRCMD.EXE` | 42447 байт |
`check_bank_calls.py` — чисто. Таблица актуализирована по финальной сборке P5
после исправления режима `O_RDONLY` в libc; P4.1 и P4.2 вместе закрывают
критерий выхода P4.
@@ -0,0 +1,215 @@
# Sprinter Commander: результаты стабилизации P5
Дата: 8 сентября 2026 года.
Статус: `PASS` в MAME 0.288 / BIOS 3.06 / DSS 1.71.57 на HDD `D:`.
Release gate PoC 0.1 закрыт в эмуляторе: пройдены граничные размеры, серия из
20 копирований, 100 refresh, повторный lifecycle, расширенная матрица ошибок,
смена носителя и запуск реальной внешней программы. Проверка на настоящем
Sprinter остаётся отдельным аппаратным smoke-тестом.
## 20 copy и граничные размеры
`tests/p5_fixture.py` создаёт двадцать файлов `F00.BIN..F19.BIN`. В набор
обязательно входят размеры:
```text
0, 1, 4095, 4096, 65535, 65536, 1048613 байт
```
Остальные тринадцать файлов проверяют повторное использование copy job,
временных имён и файловых дескрипторов. Lua-сценарий последовательно вызывает
F5 двадцать раз, не меняя активную панель. После остановки MAME
`tests/check_p5_hdd.sh` извлекает оба дерева и сравнивает каждую пару
побайтно:
```text
PASS: 20/20 файлов совпали; все граничные размеры и >1 МБ пройдены.
```
Первый вариант расписания успел выполнить только 17 команд: кадр показывал
активный прогресс `F16.BIN`, когда Lua уже отпустил следующие клавиши. Это не
трактовалось как ошибка Commander. Финальный сценарий разносит команды с
учётом числа 4-КБ блоков и получает все двадцать результатов на CHD.
## Refresh и EMM lifecycle
После copy тот же процесс получает 100 отдельных `Ctrl+R` с интервалом 0,5
секунды. Затем Commander штатно завершается и ещё дважды запускается и
завершается на том же HDD.
`sc_app_cleanup()` сравнивает `mem_info()` до выделения и после возврата общего
четырёхстраничного EMM-блока. При несовпадении программа печатает
`SPRCMD cleanup did not restore EMM free pages.` и возвращает код 4. Во всех
трёх контрольных кадрах присутствует только чистый prompt `D:\>`; повторные
инициализации также состоялись.
Артефакты:
- [прогресс файла больше 1 МБ](../artifacts/mame-p5/stability/copy-large-progress.png);
- [20 файлов после ста refresh](../artifacts/mame-p5/stability/twenty-copy-hundred-refresh.png);
- [cleanup-цикл 1](../artifacts/mame-p5/stability/cleanup-cycle-1.png);
- [cleanup-цикл 2](../artifacts/mame-p5/stability/cleanup-cycle-2.png);
- [cleanup-цикл 3](../artifacts/mame-p5/stability/cleanup-cycle-3.png).
## Реальный ENOSPC
Отдельный образ содержит `SOURCE/FULL.BIN` размером 262181 байт и filler
размером 32850000 байт. До запуска копирования на FAT16 свободно 104448 байт:
temp успевает создаться и частично вырасти, но не может вместить исходник.
Commander показал `Copy failed (errno 10); temporary file removed.` и остался
управляемым. После выхода валидатор подтвердил:
```text
PASS: ENOSPC не изменил source/filler и не оставил target/temp.
```
То есть проверен именно путь ошибки записи после создания temp, а не ранний
отказ `open()`.
Артефакты:
- [начало копирования на почти полном диске](../artifacts/mame-p5/enospc/copy-progress.png);
- [управляемый errno 10 и пустая target-панель](../artifacts/mame-p5/enospc/enospc-cleanup.png);
- [диалог после ошибки](../artifacts/mame-p5/enospc/quit-after-error.png);
- [чистый возврат в DSS](../artifacts/mame-p5/enospc/clean-dss-return.png).
## Точная граница видимой области 27/28
`N27` содержит 26 физических файлов плюс синтетический `..`, а `N28` — 27
файлов плюс `..`. Первый кадр одновременно показывает `027 entries` и
`028 entries`. После `End` панель N28 имеет `cursor=27`, первая видимая запись
становится `B0000000.TXT`, то есть `top=1`. Для N27 `End` даёт `cursor=26`,
`A0000000.TXT` остаётся первой строкой, то есть `top=0`.
- [обе точные границы](../artifacts/mame-p5/panel-edges/counts-27-28.png);
- [28-я запись включает прокрутку](../artifacts/mame-p5/panel-edges/count-28-scrolls.png);
- [27 записей помещаются без прокрутки](../artifacts/mame-p5/panel-edges/count-27-fits.png);
- [cleanup после проверки](../artifacts/mame-p5/panel-edges/clean-dss-return.png).
## Детерминированная матрица отказов copy/EXEC
`CPERR.EXE` вызывает то же банковое ядро `sc_copy_job.c`, но останавливает его
в точно известных состояниях. Это позволило проверить случаи, которые нельзя
надёжно поймать Lua-клавишей между двумя соседними 4-КБ блоками:
- отсутствующий EXE возвращает `ENOENT` (`errno 3`);
- отсутствующий source не оставляет уже созданный temp;
- отмена до первого read не создаёт target;
- после записи последнего блока job останавливается в отдельной точке
pre-commit, и отмена удаляет temp;
- принудительная ошибка read удаляет temp;
- temp, повторно открытый с `O_RDONLY`, даёт реальный отказ write
`EROFS` (`errno 8`) и удаляется;
- исчезновение temp непосредственно перед rename даёт управляемую ошибку;
- target, появившийся между begin и commit, сохраняет содержимое `KEEP`;
- после всей матрицы число свободных EMM-страниц полностью восстановлено.
Проверка выявила ошибку общего libc: исторические Sprinter-флаги имеют
значения `O_WRONLY=1`, `O_RDONLY=2`, `O_RDWR=3`, а `open()` трактовал младшие
биты как POSIX-нумерацию. Исправлены `libc/include/fcntl.h` и `libc/io/open.c`,
добавлен постоянный режимный тест в `tests/openenv`. После пересборки fast и
safe libc матрица прошла целиком, а основной F5-сценарий был повторён.
Результат внутри CHD проверяется не только по экрану: `RESULT.TXT` обязан
содержать все строки `PASS`, в `TARGET` разрешён только неизменённый
`RACE.BIN`, временные `~SC*.TMP` запрещены.
- [вся fault-matrix: ALL PASS](../artifacts/mame-p5/copy-faults/fault-matrix-all-pass.png);
- [возврат CPERR в DSS](../artifacts/mame-p5/copy-faults/return-to-dss.png).
## Смена HDD во время копирования
Lua физически выгружает только тестовый `hard2` во время первого F5 и через
несколько секунд подключает тот же CHD обратно. На DSS 1.71 активный системный
вызов ожидает возврата устройства; после подключения Commander получает
управляемый `errno 3`, удаляет temp и продолжает принимать команды. Повторный
F5 без перезапуска приложения успешно копирует `DATA.BIN` размером 262181 байт.
Посмертная проверка CHD подтверждает точное совпадение source/target и
отсутствие temp:
```text
PASS: HDD removal дал управляемый отказ; после возврата DATA совпал, temp нет.
```
- [управляемый отказ первого F5](../artifacts/mame-p5/media-change/managed-error.png);
- [прогресс повторного F5](../artifacts/mame-p5/media-change/retry-progress.png);
- [восстановленный target](../artifacts/mame-p5/media-change/recovered-copy.png);
- [чистый возврат в DSS](../artifacts/mame-p5/media-change/clean-dss-return.png).
## Реальный viewer и восстановление окружения
Внешним приложением служит не специальный test child, а полноэкранный
`examples/mdview2`. Commander перед EXEC устанавливает CWD активной панели,
поэтому viewer без передачи аргументов открывает лежащий рядом `README.MD`.
Проверены обычный вид, raw-режим, PageDown, выход и возврат в Commander с
восстановленными режимом, палитрой, CWD и обеими панелями.
Тест обнаружил в самом `mdview2` утечку scratch-блока EMM. Viewer переведён
на явное владение блоком: результат `mem_alloc_pages()` проверяется, а блок
освобождается в `unload_file()`. После исправления Commander штатно завершает
собственную lifecycle-проверку EMM.
- [главный экран mdview2](../artifacts/mame-p5/viewer/mdview-main.png);
- [mdview2 после PageDown](../artifacts/mame-p5/viewer/mdview-pagedown.png);
- [Commander после возврата](../artifacts/mame-p5/viewer/commander-restored.png);
- [чистый возврат в DSS](../artifacts/mame-p5/viewer/clean-dss-return.png).
## Финальный размер PoC
Конфигурация: `--memory big --safe --max-allocs 3000`.
| Область | Размер |
|---|---:|
| `_CODE` W2 | 8870 байт |
| данные W2 | 3240 байт |
| свободная куча W2 | 2738 байт |
| стек | 1279 байт |
| BANK1: scan/sort/copy job | 4242 / 16384 байт |
| BANK2: draw/copy/EXEC UI | 5973 / 16384 байт |
| `SPRCMD.EXE` | 42447 байт |
Проверка межбанковых вызовов чистая. Общий `make size-check` ожидаемо сообщает
рост `openenv` на 188 байт: это не скрытый рост libc, а добавленный постоянный
тест `write()` через `O_RDONLY` вместе с исправленной веткой mode mapping.
Эталон размеров автоматически не обновлялся, поскольку в общем дереве также
присутствует посторонний новый `hello3`.
## Команды воспроизведения
```sh
make hdd-p5 ALLOCS=3000
tests/run_mame_hdd.sh build/hdd/p5_stability.chd \
tests/mame_p5_stability.lua artifacts/mame-p5/local 205
tests/check_p5_hdd.sh build/hdd/p5_stability.chd
make hdd-p5-enospc ALLOCS=3000
tests/run_mame_hdd.sh build/hdd/p5_enospc.chd \
tests/mame_p5_enospc.lua artifacts/mame-p5/enospc-local 55
tests/check_p5_enospc_hdd.sh build/hdd/p5_enospc.chd
make hdd-p5-panels ALLOCS=3000
tests/run_mame_hdd.sh build/hdd/p5_panel_edges.chd \
tests/mame_p5_panel_edges.lua artifacts/mame-p5/panels-local 50
make hdd-p5-copy-faults ALLOCS=3000
tests/run_mame_hdd.sh build/hdd/p5_copy_faults.chd \
tests/mame_p5_copy_faults.lua artifacts/mame-p5/faults-local 35
tests/check_p5_copy_faults_hdd.sh build/hdd/p5_copy_faults.chd
make hdd-p5-viewer ALLOCS=3000
tests/run_mame_hdd.sh build/hdd/p5_viewer.chd \
tests/mame_p5_viewer.lua artifacts/mame-p5/viewer-local 65
make hdd-p5-media ALLOCS=3000
tests/run_mame_hdd.sh build/hdd/p5_media.chd \
tests/mame_p5_media_change.lua artifacts/mame-p5/media-local 50
tests/check_p5_media_hdd.sh build/hdd/p5_media.chd
```
Перед каждым запуском должен отсутствовать другой процесс MAME Sprinter.
`run_mame_hdd.sh` проверяет это сам. Единственный незакрытый пункт за пределами
эмуляторного release gate — повторить короткий smoke-сценарий на реальном
Sprinter Sp2000.
@@ -0,0 +1,98 @@
# Sprinter Commander: сортировки 0.2/P6
Дата: 8 сентября 2026 года.
Статус: `PASS` в MAME 0.288 / BIOS 3.06 / DSS 1.71.57 на HDD `D:`.
## Реализация
Каждая `ScPanel` хранит собственные `sort_key` и `sort_descending`. Доступны
четыре ключа:
| Комбинация | Ключ |
|---|---|
| `Ctrl+F3` | имя |
| `Ctrl+F4` | расширение |
| `Ctrl+F5` | дата и время last-write |
| `Ctrl+F6` | размер |
Выбор другого ключа включает прямой порядок; повтор текущей комбинации меняет
направление. На нижней рамке каждой панели постоянно виден режим `N/E/D/S` и
знак `+`/`-`. Refresh и переходы в каталог используют сохранённый режим этой
же панели.
Порядок групп не зависит от reverse: синтетический `..` всегда первый,
затем идут каталоги, затем обычные файлы, а красный маркер ограничения DSS
остаётся последним. Направление применяется только внутри групп. При равном
основном ключе детерминированным вторичным ключом служит имя без учёта регистра.
Дата сравнивается как FAT date, затем FAT time; отдельная creation date не
имитируется, потому что текущий `F_FIRST/F_NEXT` возвращает last-write.
Shell sort по-прежнему работает непосредственно с 24-байтовыми записями EMM
через `sc_store_get/put` и не сохраняет указатели W3. Выбранная запись после
пересортировки находится по имени; cursor/top нормализуются без повторного
сканирования каталога.
## Автоматическая проверка
`SORTRT.EXE` заполняет две разные EMM-страницы намеренно перемешанными
записями и пишет `SORTRES.TXT`. Посмертный валидатор подтвердил:
```text
PASS: 4 sort keys, reverse, fixed groups, selection, two panels.
```
Проверяются восемь точных порядков, неизменность `..` и `>>> MORE...`,
сохранение `BETA.TXT` под курсором и отсутствие влияния режима первой панели
на вторую.
Затем тот же MAME-сценарий запускает Commander, открывает `SORTL` и `SORTR`,
подаёт реальные `Ctrl+F3..F6`, переключает активную панель и делает refresh.
Два полных UI-прогона дали побайтно одинаковые контрольные кадры.
- [name ascending](../artifacts/mame-p6/sort/name-ascending.png);
- [extension ascending](../artifacts/mame-p6/sort/extension-ascending.png);
- [extension descending](../artifacts/mame-p6/sort/extension-descending.png);
- [size ascending](../artifacts/mame-p6/sort/size-ascending.png);
- [size descending](../artifacts/mame-p6/sort/size-descending.png);
- [date ascending](../artifacts/mame-p6/sort/date-ascending.png);
- [date descending](../artifacts/mame-p6/sort/date-descending.png);
- [left `S-`, right `E+`](../artifacts/mame-p6/sort/independent-panels.png);
- [refresh сохранил `E+`](../artifacts/mame-p6/sort/refresh-keeps-mode.png);
- [штатный cleanup](../artifacts/mame-p6/sort/clean-dss-return.png).
В текущем общем HDD-builder файлы fixture получают одинаковую дату при
копировании дерева, поэтому точный порядок date проверяет target-проба;
UI-сценарий независимо подтверждает доставку `Ctrl+F5`, оба состояния `D+/-`
и обратный redraw. Этот предел fixture не выдаётся за проверку разных FAT-дат
по скриншоту.
## Размер
Конфигурация `--memory big --safe --max-allocs 3000`:
| Область | P5 | После сортировок | Изменение |
|---|---:|---:|---:|
| `_CODE` W2 | 8870 | 9125 | +255 |
| данные W2 | 3240 | 3244 | +4 |
| свободная куча W2 | 2738 | 2479 | -259 |
| BANK1 | 4242 | 5137 | +895 |
| BANK2 | 5973 | 6053 | +80 |
| `SPRCMD.EXE` | 42447 | 42702 | +255 |
Рост BANK1 содержит компараторы и сохранение выбора, W2 — команды и один
банковый вызов. Первоначальный вариант с восемью статусными строками давал
рост W2 на 736 байт и был сокращён до общего статуса плюс компактный индикатор
на рамке. Межбанковый аудит финальной сборки чистый.
## Воспроизведение
```sh
make hdd-p6-sort ALLOCS=3000
tests/run_mame_hdd.sh build/hdd/p6_sort.chd \
tests/mame_p6_sort.lua artifacts/mame-p6/local 90
tests/check_p6_sort_hdd.sh build/hdd/p6_sort.chd
```
Перед запуском требуется убедиться, что другого MAME Sprinter нет;
`run_mame_hdd.sh` выполняет эту проверку.
@@ -0,0 +1,73 @@
# Sprinter Commander: выбор записей 0.2/P7
Дата: 8 сентября 2026 года.
Статус: `PASS` в MAME 0.288 / BIOS 3.06 / DSS 1.71.57 на HDD `D:`.
## Контракт
- `Insert` меняет выбор текущей обычной записи и передвигает cursor вниз;
- `*` инвертирует выбор только файлов текущей панели;
- файлы и каталоги можно выбирать, синтетический `..` и виртуальный маркер
DSS нельзя;
- групповые `+`, `-` и `*` игнорируют каталоги и не снимают выбор,
ранее поставленный с каталога через `Insert`;
- выбранная запись отмечается `*` слева от имени;
- footer активной панели показывает `<bytes> bytes in <count> selected`, а
общая строка информации пока сохраняет компактное `sel размер/количество`;
- размер каталога в этой статистике равен нулю: рекурсивный подсчёт появится
вместе с групповой job;
- сортировка переносит `SC_ENTRY_SELECTED` вместе с записью и сохраняет
текущий файл под cursor;
- refresh текущего среза явно очищает выбор; режим сортировки сохраняется;
- панели имеют независимые EMM-страницы и независимую статистику.
Функции выбора находятся в BANK1 и работают только через
`sc_store_get/put`; указатель на W3 не сохраняется. Для toggle и invert
используется один банковый entry point, чтобы не плодить resident thunk’и.
## Проверка
Target-проба `SELRT.EXE` проверяет Insert двух файлов, точную сумму 30,
отказы для `..`/DSS-маркера, две инверсии, выбор каталога, сброс статистики
через `sc_store_clear()` и возврат EMM. Результат извлекается из CHD:
```text
PASS: Insert/invert/masks, virtual guards, totals, refresh и EMM.
```
UI-сценарий выбирает два каталога и `A.BIN`, меняет порядок на `N-`,
инвертирует выбор и перечитывает каталог. Контрольные состояния:
- [виртуальная запись не выбирается](../artifacts/mame-p7/select/virtual-entry-guard.png);
- [три записи, `sel 10/003`](../artifacts/mame-p7/select/insert-three.png);
- [сортировка сохранила flags и cursor](../artifacts/mame-p7/select/sort-keeps-selection.png);
- [инверсия файлов не изменила ранее выбранные каталоги, `sel 50/004`](../artifacts/mame-p7/select/inverted.png);
- [refresh очистил выбор и сохранил `N-`](../artifacts/mame-p7/select/refresh-clears.png);
- [штатный возврат в DSS](../artifacts/mame-p7/select/clean-dss-return.png).
## Размер
Конфигурация `--memory big --safe --max-allocs 3000`:
| Область | После P6 | После P7 | Изменение |
|---|---:|---:|---:|
| `_CODE` W2 | 9125 | 9434 | +309 |
| данные W2 | 3244 | 3256 | +12 |
| свободная куча W2 | 2479 | 2158 | -321 |
| BANK1 | 5137 | 6033 | +896 |
| BANK2 | 6053 | 6213 | +160 |
| `SPRCMD.EXE` | 42702 | 43011 | +309 |
Первый вариант имел два банковых entry point и оставлял 2069 байт heap.
После объединения и сокращения резидентных сообщений финальная сборка
возвращает 2158 байт. Межбанковый аудит чистый.
## Воспроизведение
```sh
make hdd-p7-select ALLOCS=3000
tests/run_mame_hdd.sh build/hdd/p7_select.chd \
tests/mame_p7_select.lua artifacts/mame-p7/local 65
tests/check_p7_select_hdd.sh build/hdd/p7_select.chd
```
@@ -0,0 +1,69 @@
# Sprinter Commander: выбор по маске 0.2/P8
Дата: 8 сентября 2026 года.
Статус: `PASS` в MAME 0.288 / BIOS 3.06 / DSS 1.71.57 на HDD `D:`.
## Реализация
Обычный `+` открывает `Select by mask`, обычный `-``Unselect by mask`.
Начальное значение `*.*`; Enter применяет действие, Esc отменяет его без
изменения панели. Доступны wildcard `*` и `?`, сравнение ASCII-букв не зависит
от регистра. Как в DOS, `*.*` означает все имена файлов, включая имена без точки.
Все групповые операции `+`, `-`, `*` игнорируют каталоги; синтетический `..` и виртуальный
маркер DSS также не участвуют. Выбор, поставленный на каталог отдельным `Insert`, не меняется.
Модальный редактор не использует консольные окна или прямой VRAM. Диалог и
поле находятся в общей EMM-модели Commander и выводятся координатными
функциями. Реализованы insertion в позиции cursor, Backspace, Delete,
Left/Right, Home/End, Enter и Esc. Для текущих DSS 8.3-масок ввод ограничен
12 символами; тот же editor будет переиспользован командами F7/F6 и
командной строкой.
Существующий `sc_dialog.c` вместе с новым editor перенесён из HOME в BANK2.
Mask matcher находится в BANK1 рядом с выбором; приложение передаёт ему
резидентный scratch-буфер copy job, пока файловая job не активна.
## Проверка
Target-часть проверяет:
- `*.bin` в нижнем регистре против имён верхнего регистра;
- `?.b?n`;
- `*.*` для файла без точки и неизменность каталога;
- последовательное set/clear с точными count/size;
- неизменность `..` и DSS-маркера.
UI-сценарий редактирует `*.*` в `*.bin`, получает три выбранных файла и
`sel 60/003`, затем снимает `b.bin`, получая `sel 40/002`. Следующий диалог
закрывается Esc, состояние не меняется.
- [диалог с `*.*`](../artifacts/mame-p8/masks/default-dialog.png);
- [поле после редактирования в `*.bin`](../artifacts/mame-p8/masks/edited-bin-mask.png);
- [три BIN выбраны](../artifacts/mame-p8/masks/bin-selected.png);
- [`b.bin` снят](../artifacts/mame-p8/masks/b-unselected.png);
- [Esc сохранил выбор](../artifacts/mame-p8/masks/cancel-preserves.png);
- [штатный cleanup](../artifacts/mame-p8/masks/clean-dss-return.png).
## Размер
| Область | После P7 | После P8 | Изменение |
|---|---:|---:|---:|
| `_CODE` W2 | 9434 | 9416 | -18 |
| данные W2 | 3256 | 3256 | 0 |
| свободная куча W2 | 2158 | 2176 | +18 |
| BANK1 | 6033 | 6451 | +418 |
| BANK2 | 6213 | 7268 | +1055 |
| `SPRCMD.EXE` | 43011 | 42993 | -18 |
Несмотря на новую функцию, HOME стал меньше благодаря переносу диалогов.
Оба банка имеют более 9 КБ резерва; межбанковый аудит чистый.
## Воспроизведение
```sh
make hdd-p8-masks ALLOCS=3000
tests/run_mame_hdd.sh build/hdd/p7_select.chd \
tests/mame_p7_select.lua artifacts/mame-p8/local 80
tests/check_p7_select_hdd.sh build/hdd/p7_select.chd
```
@@ -0,0 +1,71 @@
# Sprinter Commander: создание каталога 0.2/P9
Дата: 8 сентября 2026 года.
Статус: `PASS` в MAME 0.288 / BIOS 3.06 / DSS 1.71.57 на HDD `D:`.
## Контракт
- `F7` открывает общий координатный редактор и принимает один компонент
DOS 8.3 длиной до 12 символов;
- разрешены база до 8 и расширение до 3 символов, одна необязательная точка,
ASCII-буквы/цифры, допустимая DOS-пунктуация и CP866-байты `80h..FFh`;
- пустое имя, `.`/`..`, разделители пути, пробелы, wildcard, лишние точки и
превышение 8.3 отвергаются до вызова DSS;
- полный путь строится в уже принадлежащем `ScApp` scratch-буфере;
- `mkdir()` получает полный путь и не меняет глобальный CWD DSS;
- после успеха перечитывается только активная панель;
- существующий каталог и невалидное имя дают управляемое сообщение, `Esc`
закрывает редактор без файловой операции.
Валидатор вынесен в отдельный банковый модуль `sc_name.c`. Сам Commander
размещает редактор, оркестрацию F7 и валидатор в BANK2; HOME/W2 содержит
только dispatch и банковые трамплины.
## Проверка
Target-проба `MKRT.EXE` проверяет допустимые и запрещённые имена, построение
полного пути, реальный цикл `mkdir`/duplicate/`rmdir` и неизменность CWD.
После MAME дерево извлекается из CHD: внешний checker подтверждает созданный
`MKTEST/NEWDIR`, неизменный `KEEP.TXT` и отсутствие пробных каталогов.
```text
PASS: validator, mkdir/error/rmdir/CWD и F7 NEWDIR.
```
UI-сценарий отдельно подтверждает:
- [пустой редактор F7](../artifacts/mame-p9/mkdir/dialog-empty.png);
- [создание и refresh активной панели](../artifacts/mame-p9/mkdir/created.png);
- [управляемую ошибку повторного имени](../artifacts/mame-p9/mkdir/duplicate-error.png);
- [отказ для имени длиннее 8.3](../artifacts/mame-p9/mkdir/invalid-name.png);
- [вход в созданный каталог](../artifacts/mame-p9/mkdir/entered-newdir.png);
- [штатный возврат в DSS](../artifacts/mame-p9/mkdir/clean-dss-return.png).
## Размер
Конфигурация `--memory big --safe --max-allocs 3000`:
| Область | После P8 | После P9 и уточнения group select | Изменение |
|---|---:|---:|---:|
| `_CODE` W2 | 9416 | 9449 | +33 |
| данные W2 | 3256 | 3256 | 0 |
| свободная куча W2 | 2176 | 2143 | -33 |
| BANK1 | 6451 | 6477 | +26 |
| BANK2 | 7268 | 8156 | +888 |
| `SPRCMD.EXE` | 42993 | 43026 | +33 |
Оба банка сохраняют более 8 КБ резерва. Межбанковый аудит чист.
Общий `size-check` по всему C-Compiler пока ожидаемо отмечает посторонний
новый `hello3` и намеренный рост `openenv` после постоянной проверки
`O_RDONLY`; эталон размеров из Commander не обновлялся.
## Воспроизведение
```sh
make hdd-p9-mkdir ALLOCS=3000
tests/run_mame_hdd.sh build/hdd/p9_mkdir.chd \
tests/mame_p9_mkdir.lua artifacts/mame-p9/local 70
tests/check_p9_mkdir_hdd.sh build/hdd/p9_mkdir.chd
```
@@ -0,0 +1,85 @@
# Sprinter Commander PoC 0.1
Дата: 8 сентября 2026 года.
Статус: техническая архитектура подтверждена в MAME 0.288 / BIOS 3.06 /
DSS 1.71.57. `SPRCMD.EXE` является proof of concept, а не готовым файловым
менеджером для повседневной работы. Аппаратный smoke-тест на Sprinter Sp2000
ещё требуется.
## Что уже работает
- полноэкранный двухпанельный интерфейс 80x32 через безоконные координатные
функции BIOS/ESTEX, без прямой записи в VRAM;
- независимые пути и навигация обеих панелей, синтетический `..`, Home/End,
PgUp/PgDn, Tab и refresh;
- одна EMM-страница на панель, максимум 640 записей без заполнения страницы
целиком, красный `>>> MORE...` при известном ограничении DSS;
- каталоги перед файлами и сортировка по имени;
- безопасное копирование одного файла по F5 через отдельную EMM-страницу W3,
4-КБ блоками и temp/rename commit;
- отмена по Esc между блоками и в отдельной точке перед commit;
- запрет перезаписи уже существующего target;
- запуск `.EXE` по Enter с временным CWD активной панели и восстановлением
экрана, палитры, CWD и панелей;
- штатное освобождение EMM, закрытие fd и восстановление системного экрана
при выходе.
## Сборка и запуск
Из каталога приложения:
```sh
make ALLOCS=3000
make hdd
```
Результат — `build/sprcmd.exe`; образ для быстрого интеграционного запуска —
`build/hdd/sprcmd.chd`. Для MAME штатно используются системный HDD как `C:` и
отдельный тестовый `hard2` как `D:`. Одновременно разрешён только один процесс
MAME Sprinter.
Полный перечень автоматизированных образов и команд находится в
[test-media.md](test-media.md), доказательства P5 — в
[p5-stability-results.md](p5-stability-results.md).
Финальная конфигурация PoC: `--memory big --safe --max-allocs 3000`;
`SPRCMD.EXE` — 42447 байт, `_CODE` W2 — 8870 байт, свободная куча W2 —
2738 байт, BANK1 — 4242 байта, BANK2 — 5973 байта.
## Клавиши PoC
| Клавиша | Действие |
|---|---|
| стрелки, Home/End, PgUp/PgDn | перемещение по активной панели |
| `Tab` | сменить активную панель |
| `Enter` | войти в каталог/`..` или запустить `.EXE` |
| `Ctrl+R` | перечитать активную панель |
| `F5` | скопировать текущий обычный файл в пассивную панель |
| `Esc` | отменить активное копирование |
| `F10` | подтверждение выхода |
Надписи F6/F7/F8 и меню пока являются зарезервированными командами следующих
этапов, а не обещанием выполненной операции.
## Известные ограничения
- в панели показывается не более 640 записей; многостраничный режим относится
только к 2.0+;
- стабильный DSS 1.71 может остановить физическое перечисление около 512-й
FAT-записи с ошибкой 35 — показывается информирующий красный маркер;
- сортировка PoC только по имени; ext/size/date и обратный порядок входят в
следующую функциональную фазу;
- нет выбора нескольких файлов, масок, очереди и рекурсивных операций;
- F6 move/rename, F7 mkdir, F8 delete и overwrite policy ещё не реализованы;
- нет командной строки, `Ctrl+O`, истории, associations и передачи аргументов
внешней программе;
- нет конфигурационного файла цветов, viewer/editor вызываются только как
обычные внешние `.EXE`;
- нет длинных имён: DSS-контракт текущего слоя — 8.3;
- floppy поддерживается лишь как медленный smoke-носитель; основная матрица
выполнялась на HDD `D:`;
- проверка на реальном Sprinter пока не выполнена.
Эти ограничения намеренно отделяют доказанную платформенную основу PoC от
безопасного файлового ядра 0.2 и полного набора функций версии 1.0.
+90
View File
@@ -0,0 +1,90 @@
# Носители для тестов Commander
Дата: 8 сентября 2026 года.
## Решение
Стандартный носитель интеграционных и нагрузочных тестов Commander —
отдельный FAT16 HDD-образ, подключаемый к MAME как `hard2`. DSS видит его как
диск `D:`; системный `hard1` остаётся диском `C:`.
MAME качественно эмулирует механические задержки FDD. Это полезно для проверки
совместимости, но делает повторяющиеся scan/copy-прогоны на порядок медленнее и
не даёт пользы большинству функциональных тестов. Поэтому FDD `A:` сохраняется
только для отдельных smoke-тестов чтения/запуска с дискеты.
## Образы
Все рабочие образы Commander лежат локально в `build/hdd/` и не подменяют
общий `mame/v306/IMG/test_hdd.chd`:
```sh
make hdd # только SPRCMD.EXE -> build/hdd/sprcmd.chd
make hdd-stress # P3: EMPTY/MANY/BIGDIR -> build/hdd/p3_stress.chd
make hdd-copy # P4: COPYFROM/COPYTO -> build/hdd/p4_copy.chd
make hdd-exec # P4: EXECLEFT/EXECRGHT -> build/hdd/p4_exec.chd
make hdd-p5 # P5: 20 copy + resource cycles -> p5_stability.chd
make hdd-p5-enospc # P5: почти полный HDD -> p5_enospc.chd
make hdd-p5-panels # P5: 27/28 записей -> p5_panel_edges.chd
make hdd-p5-copy-faults # P5: copy/EXEC fault matrix -> p5_copy_faults.chd
make hdd-p5-viewer # P5: SPRCMD + реальный mdview2 -> p5_viewer.chd
make hdd-p5-media # P5: hot-unplug hard2 во время F5 -> p5_media.chd
make hdd-p6-sort # 0.2: target/UI сортировки -> p6_sort.chd
make hdd-p7-select # 0.2: target/UI выбора -> p7_select.chd
make hdd-p8-masks # 0.2: расширение того же образа масками +/−
make hdd-p9-mkdir # 0.2: validator и UI F7 mkdir -> p9_mkdir.chd
make hdd-p10-rename # 0.2: file/dir rename и UI F6 -> p10_rename.chd
make hdd-p11-delete # 0.2: confirm/cancel и UI F8 -> p11_delete.chd
make hdd-p12-group-copy # 0.2: выбранные файлы -> p12_group_copy.chd
make hdd-p13-group-delete # 0.2: выбранные файлы -> p13_group_delete.chd
make hdd-p14-default-colors # UI: dir/EXE/file -> p14_default_colors.chd
make hdd-p15-tree-copy # 0.2: recursive current dir -> p15_tree_copy.chd
make hdd-p15-tree-cancel # 0.2: cancel/cleanup -> p15_tree_cancel.chd
make hdd-p15-tree-self # 0.2: self-target guard -> p15_tree_self.chd
make hdd-p16-group-tree-copy # 0.2: multi-root F5 -> p16_group_tree_copy.chd
make hdd-p17-tree-delete # 0.2: recursive group F8 -> p17_tree_delete.chd
make hdd-p17-tree-delete-cancel # runtime Esc -> p17_tree_delete_cancel.chd
make hdd-p18-metadata # exact FAT metadata F5 -> p18_metadata.chd
make hdd-p19-overwrite # backup/rollback probe -> p19_overwrite.chd
make hdd-p19-policy # UI conflict policy -> p19_policy.chd
```
Сборщик `toolchain/make_hdd.sh` принимает как отдельные файлы, так и готовые
деревья каталогов. Рекурсивное копирование дерева одной командой особенно
важно для `BIGDIR`: 680 файлов не требуют 680 отдельных запусков `mcopy`.
## Запуск и проверка
Lua-сценарий сначала вводит `D:`, а затем запускает `SPRCMD.EXE`. Благодаря
этому текущий диск DSS и оба относительных контекста панелей находятся на
тестовом HDD, а не на системном `C:` или медленном `A:`.
Пример полного теста F5:
```sh
make hdd-copy ALLOCS=3000
tests/run_mame_hdd.sh \
build/hdd/p4_copy.chd tests/mame_p4_copy_hdd.lua \
artifacts/mame-p4/copy-hdd-local 75
tests/check_p4_copy_hdd.sh build/hdd/p4_copy.chd
```
После пересборки CHD MAME требуется полностью перезапустить. Нельзя извлекать
или проверять образ во время работающего MAME: окончательное состояние
валидируется только после чистого выхода эмулятора.
Скриншоты подтверждают состояние интерфейса, но не заменяют файловую
проверку. Для copy-теста после MAME CHD преобразуется обратно в RAW, файлы
извлекаются через mtools и сравниваются с детерминированными шаблонами.
Для EXEC-теста аналогичная тройка команд использует
`tests/mame_p4_exec_hdd.lua` и `tests/check_p4_exec_hdd.sh`. Все имена,
которые должен открыть DSS, задаются в формате 8.3: VFAT long name на образе
не является доступным DSS-именем и может отображаться как alias с `~1`.
Сценарий смены носителя управляет только тестовым `hard2`. Перед запуском
любой автоматизации проверяется, что другого экземпляра MAME Sprinter нет:
одновременный доступ двух процессов к CHD запрещён. После физического unload
DSS может ожидать возврата устройства внутри системного вызова; Lua возвращает
тот же образ, после чего Commander получает управляемую ошибку и продолжает
работу.
+53
View File
@@ -0,0 +1,53 @@
# Sprinter Commander: направление развития UI
Дата: 8 сентября 2026 года.
## Референс
Volkov Commander используется как поведенческий и композиционный образец,
но интерфейс не переносится побайтно. Цель — узнаваемая двухпанельная модель
VC, адаптированная к текстовому экрану Sprinter 80x32 и к координатному
BIOS/ESTEX backend без консольных окон и прямого доступа к VRAM.
По предоставленным контрольным экранам фиксируются следующие ориентиры:
- независимый режим панели `Brief` с несколькими колонками коротких имён;
- режим `Details` с колонками `Name`, `Size`, `Date`, `Time`;
- строка итога выбора внизу соответствующей панели вида
`N bytes in M selected files`;
- верхняя полоса меню, открываемая `F9`, с разделами панелей, файлов,
команд и настроек;
- однострочный prompt под панелями и полоса функциональных клавиш внизу;
- путь активной панели должен читаться как единая строка и визуально
отличаться от содержимого панели;
- активность панели определяется cursor/title, а не лишними служебными
словами внутри рамки.
## Поэтапное сближение
1. В 0.2 сохранить устойчивую текущую геометрию, но заменить диагностическую
статистику выбора пользовательской строкой итога и привести подписи
F-клавиш к реально работающим командам. Строка итога в footer панели уже
реализована и подтверждена
[кадром MAME](../artifacts/mame-ui/selected-footer-clean.png);
окончательная геометрия появится вместе с режимами панелей.
2. После безопасных F6/F7/F8 добавить модель режима панели и `Details` с
заголовками колонок; затем `Brief` с расчётом числа колонок из ширины.
3. В 0.4 добавить верхнее меню `F9`, настоящую командную строку и
полноэкранную псевдооболочку `Ctrl+O` на общем редакторе строки.
4. Стандартный fallback цветов уже добавлен: белые каталоги, жёлтые `.EXE`,
светло-серые остальные файлы. После стабилизации режимов добавить загрузку
конфигурируемых правил расширений и сохранение режима отдельно для левой и
правой панели.
До появления отдельного счётчика файлов строка итога не должна называть
каталоги файлами: `Insert` может выбирать каталог, тогда как групповые
`+`, `-`, `*` его обязаны игнорировать. Это различие остаётся частью модели,
а не только правилом renderer.
## Проверка
Каждый визуальный шаг принимается не по одному кадру, а по сценарию с
известным содержимым панели, соседним контрольным кадрам и, где применимо,
извлечённому состоянию HDD. Зона имитации индикаторов дисководов MAME не
участвует в сравнении интерфейса Commander.