216 lines
13 KiB
Markdown
216 lines
13 KiB
Markdown
# 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 180
|
||
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.
|