# 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 ```