diff --git a/applications/Volkov/.gitignore b/applications/Volkov/.gitignore new file mode 100644 index 0000000..b307c25 --- /dev/null +++ b/applications/Volkov/.gitignore @@ -0,0 +1,8 @@ +# Локальные исполняемые файлы платформенных проб собираются рядом с +# исходниками. Общий шаблон репозитория не покрывает этот дополнительный +# уровень вложенности applications/Volkov/tests//. +/tests/*/*.exe + +# Клон исторических исходников VC служит локальным справочным материалом и +# сохраняет собственный .git; во внешний репозиторий его как gitlink не кладём. +/VolkovCommander/ diff --git a/applications/Volkov/Makefile b/applications/Volkov/Makefile new file mode 100644 index 0000000..fde9978 --- /dev/null +++ b/applications/Volkov/Makefile @@ -0,0 +1,222 @@ +# Sprinter Commander — двухпанельный файловый менеджер. + +PROJ_ROOT := $(abspath $(CURDIR)/../..) +EXAMPLE := sprcmd +SRC_DIR := src +BUILD_DIR := build +MEMORY := big +ALLOCS ?= 3000 +HDD_IMG := $(CURDIR)/build/hdd/sprcmd.chd +MDVIEW2_DIR := $(PROJ_ROOT)/examples/mdview2 +MDVIEW2_SRCS := $(wildcard $(MDVIEW2_DIR)/*.c) +MDVIEW2_HEADERS := $(wildcard $(MDVIEW2_DIR)/*.h) +MDVIEW2_TEST_EXE := $(CURDIR)/build/external/mdview2.exe +P5_COPY_FAULT_EXE := $(CURDIR)/build/tests/cperr.exe +P6_SORT_TEST_EXE := $(CURDIR)/build/tests/sortrt.exe +P7_SELECT_TEST_EXE := $(CURDIR)/build/tests/selrt.exe +P9_MKDIR_TEST_EXE := $(CURDIR)/build/tests/mkrt.exe +P10_RENAME_TEST_EXE := $(CURDIR)/build/tests/renrt.exe +P11_DELETE_TEST_EXE := $(CURDIR)/build/tests/delrt.exe +P19_OVERWRITE_TEST_EXE := $(CURDIR)/build/tests/p19/delrt.exe +BANK1_SRCS := src/sc_source_fs.c src/sc_sort.c src/sc_select.c \ + src/sc_copy_job.c src/sc_metadata.c src/sc_disk.c src/sc_tree.c src/sc_exec_ui.c +BANK2_SRCS := src/sc_draw.c src/sc_dialog.c src/sc_copy_ui.c \ + src/sc_file_ui.c src/sc_name.c src/sc_rename.c +EXTRA_FLAGS := --safe --max-allocs $(ALLOCS) -I include \ + $(foreach f,$(BANK1_SRCS),--bank 1=$(f)) \ + $(foreach f,$(BANK2_SRCS),--bank 2=$(f)) +EXTRA_SRCS := \ + src/sc_app.c \ + src/sc_command.c \ + src/sc_input.c \ + src/sc_path.c \ + src/sc_panel.c \ + src/sc_platform_screen.c \ + src/sc_platform_exec.c \ + src/sc_screen.c \ + src/sc_store.c \ + src/sc_theme.c \ + src/sc_video_system.c + +include $(PROJ_ROOT)/app.mk + +HEADERS := $(wildcard include/*.h) + +$(EXE): Makefile $(HEADERS) $(BANK1_SRCS) $(BANK2_SRCS) + +hdd-stress: $(EXE) + sh tests/make_p3_stress_hdd.sh $(CURDIR)/build/hdd/p3_stress.chd $(EXE) + +hdd-copy: $(EXE) + sh tests/make_p4_copy_hdd.sh $(CURDIR)/build/hdd/p4_copy.chd $(EXE) + +tests/p4_exec_child/p4child.exe: tests/p4_exec_child/p4child.c \ + tests/p4_exec_child/Makefile + $(MAKE) -C tests/p4_exec_child ALLOCS=$(ALLOCS) + +hdd-exec: $(EXE) tests/p4_exec_child/p4child.exe + sh tests/make_p4_exec_hdd.sh $(CURDIR)/build/hdd/p4_exec.chd \ + $(EXE) tests/p4_exec_child/p4child.exe + +hdd-p5: $(EXE) + sh tests/make_p5_hdd.sh $(CURDIR)/build/hdd/p5_stability.chd $(EXE) + +hdd-p5-enospc: $(EXE) + sh tests/make_p5_enospc_hdd.sh \ + $(CURDIR)/build/hdd/p5_enospc.chd $(EXE) + +hdd-p5-panels: $(EXE) + sh tests/make_p5_panel_edges_hdd.sh \ + $(CURDIR)/build/hdd/p5_panel_edges.chd $(EXE) + +$(MDVIEW2_TEST_EXE): $(MDVIEW2_SRCS) $(MDVIEW2_HEADERS) $(LIB) + mkdir -p $(dir $@) + $(PROJ_ROOT)/bin/sprinter-cc --memory small --max-allocs $(ALLOCS) \ + -o $@ $(MDVIEW2_SRCS) + +hdd-p5-viewer: $(EXE) $(MDVIEW2_TEST_EXE) + sh tests/make_p5_viewer_hdd.sh \ + $(CURDIR)/build/hdd/p5_viewer.chd $(EXE) \ + $(MDVIEW2_TEST_EXE) $(MDVIEW2_DIR)/README.md + +$(P5_COPY_FAULT_EXE): tests/p5_copy_faults.c src/sc_copy_job.c \ + src/sc_metadata.c src/sc_disk.c \ + src/sc_path.c src/sc_platform_exec.c include/sc_copy.h \ + include/sc_metadata.h include/sc_disk.h include/sc_path.h \ + include/sc_exec.h $(LIB) + mkdir -p $(dir $@) + $(PROJ_ROOT)/bin/sprinter-cc --memory big --safe --max-allocs $(ALLOCS) \ + -I include --bank 1=src/sc_copy_job.c --bank 1=src/sc_metadata.c \ + --bank 1=src/sc_disk.c -o $@ \ + tests/p5_copy_faults.c src/sc_path.c src/sc_platform_exec.c + +hdd-p5-copy-faults: $(P5_COPY_FAULT_EXE) + sh tests/make_p5_copy_faults_hdd.sh \ + $(CURDIR)/build/hdd/p5_copy_faults.chd $(P5_COPY_FAULT_EXE) + +hdd-p5-media: $(EXE) + sh tests/make_p5_media_hdd.sh \ + $(CURDIR)/build/hdd/p5_media.chd $(EXE) + +$(P6_SORT_TEST_EXE): tests/p6_sort_probe.c src/sc_sort.c src/sc_store.c \ + include/sc_entry.h include/sc_panel.h include/sc_sort.h \ + include/sc_store.h $(LIB) + mkdir -p $(dir $@) + $(PROJ_ROOT)/bin/sprinter-cc --memory big --safe --max-allocs $(ALLOCS) \ + -I include --bank 1=src/sc_sort.c -o $@ \ + tests/p6_sort_probe.c src/sc_store.c + +hdd-p6-sort: $(EXE) $(P6_SORT_TEST_EXE) + sh tests/make_p6_sort_hdd.sh $(CURDIR)/build/hdd/p6_sort.chd \ + $(EXE) $(P6_SORT_TEST_EXE) + +$(P7_SELECT_TEST_EXE): tests/p7_select_probe.c src/sc_select.c \ + src/sc_store.c include/sc_entry.h include/sc_panel.h \ + include/sc_select.h include/sc_store.h $(LIB) + mkdir -p $(dir $@) + $(PROJ_ROOT)/bin/sprinter-cc --memory big --safe --max-allocs $(ALLOCS) \ + -I include --bank 1=src/sc_select.c -o $@ \ + tests/p7_select_probe.c src/sc_store.c + +hdd-p7-select: $(EXE) $(P7_SELECT_TEST_EXE) + sh tests/make_p7_select_hdd.sh $(CURDIR)/build/hdd/p7_select.chd \ + $(EXE) $(P7_SELECT_TEST_EXE) + +hdd-p8-masks: hdd-p7-select + +$(P9_MKDIR_TEST_EXE): tests/p9_mkdir_probe.c src/sc_name.c src/sc_path.c \ + include/sc_path.h $(LIB) + mkdir -p $(dir $@) + $(PROJ_ROOT)/bin/sprinter-cc --memory big --safe --max-allocs $(ALLOCS) \ + -I include --bank 1=src/sc_name.c -o $@ \ + tests/p9_mkdir_probe.c src/sc_path.c + +hdd-p9-mkdir: $(EXE) $(P9_MKDIR_TEST_EXE) + sh tests/make_p9_mkdir_hdd.sh $(CURDIR)/build/hdd/p9_mkdir.chd \ + $(EXE) $(P9_MKDIR_TEST_EXE) + +$(P10_RENAME_TEST_EXE): tests/p10_rename_probe.c src/sc_rename.c \ + include/sc_file_ops.h $(LIB) + mkdir -p $(dir $@) + $(PROJ_ROOT)/bin/sprinter-cc --memory big --safe --max-allocs $(ALLOCS) \ + -I include --bank 1=src/sc_rename.c -o $@ \ + tests/p10_rename_probe.c + +hdd-p10-rename: $(EXE) $(P10_RENAME_TEST_EXE) + sh tests/make_p10_rename_hdd.sh $(CURDIR)/build/hdd/p10_rename.chd \ + $(EXE) $(P10_RENAME_TEST_EXE) + +$(P11_DELETE_TEST_EXE): tests/p11_delete_probe.c $(LIB) + mkdir -p $(dir $@) + $(PROJ_ROOT)/bin/sprinter-cc --memory big --safe --max-allocs $(ALLOCS) \ + -o $@ tests/p11_delete_probe.c + +hdd-p11-delete: $(EXE) $(P11_DELETE_TEST_EXE) + sh tests/make_p11_delete_hdd.sh $(CURDIR)/build/hdd/p11_delete.chd \ + $(EXE) $(P11_DELETE_TEST_EXE) + +hdd-p12-group-copy: $(EXE) + sh tests/make_p12_group_copy_hdd.sh \ + $(CURDIR)/build/hdd/p12_group_copy.chd $(EXE) + +hdd-p13-group-delete: $(EXE) + sh tests/make_p13_group_delete_hdd.sh \ + $(CURDIR)/build/hdd/p13_group_delete.chd $(EXE) + +hdd-p14-default-colors: $(EXE) + sh tests/make_p14_default_colors_hdd.sh \ + $(CURDIR)/build/hdd/p14_default_colors.chd $(EXE) + +hdd-p15-tree-copy: $(EXE) + sh tests/make_p15_tree_copy_hdd.sh \ + $(CURDIR)/build/hdd/p15_tree_copy.chd $(EXE) + +hdd-p15-tree-cancel: $(EXE) + sh tests/make_p15_tree_cancel_hdd.sh \ + $(CURDIR)/build/hdd/p15_tree_cancel.chd $(EXE) + +hdd-p15-tree-self: $(EXE) + sh tests/make_p15_tree_self_hdd.sh \ + $(CURDIR)/build/hdd/p15_tree_self.chd $(EXE) + +hdd-p16-group-tree-copy: $(EXE) + sh tests/make_p16_group_tree_copy_hdd.sh \ + $(CURDIR)/build/hdd/p16_group_tree_copy.chd $(EXE) + +hdd-p17-tree-delete: $(EXE) + sh tests/make_p17_tree_delete_hdd.sh \ + $(CURDIR)/build/hdd/p17_tree_delete.chd $(EXE) + +hdd-p17-tree-delete-cancel: $(EXE) + sh tests/make_p17_tree_delete_cancel_hdd.sh \ + $(CURDIR)/build/hdd/p17_tree_delete_cancel.chd $(EXE) + +hdd-p18-metadata: $(EXE) + sh tests/make_p18_metadata_hdd.sh \ + $(CURDIR)/build/hdd/p18_metadata.chd $(EXE) + +$(P19_OVERWRITE_TEST_EXE): tests/p19_overwrite_probe.c \ + src/sc_copy_job.c src/sc_metadata.c src/sc_disk.c src/sc_path.c \ + include/sc_copy.h include/sc_metadata.h include/sc_disk.h \ + include/sc_path.h $(LIB) + mkdir -p $(dir $@) + $(PROJ_ROOT)/bin/sprinter-cc --memory big --safe --max-allocs $(ALLOCS) \ + -I include --bank 1=src/sc_copy_job.c --bank 1=src/sc_metadata.c \ + --bank 1=src/sc_disk.c \ + -o $@ tests/p19_overwrite_probe.c src/sc_path.c + +hdd-p19-overwrite: $(P19_OVERWRITE_TEST_EXE) + sh tests/make_p19_overwrite_hdd.sh \ + $(CURDIR)/build/hdd/p19_overwrite.chd $(P19_OVERWRITE_TEST_EXE) + +hdd-p19-policy: $(EXE) + sh tests/make_p19_policy_hdd.sh \ + $(CURDIR)/build/hdd/p19_policy.chd $(EXE) + +.PHONY: hdd-stress hdd-copy hdd-exec hdd-p5 hdd-p5-enospc hdd-p5-panels \ + hdd-p5-viewer hdd-p5-copy-faults hdd-p5-media hdd-p6-sort hdd-p7-select \ + hdd-p8-masks hdd-p9-mkdir hdd-p10-rename hdd-p11-delete \ + hdd-p12-group-copy hdd-p13-group-delete hdd-p14-default-colors \ + hdd-p15-tree-copy hdd-p15-tree-cancel hdd-p15-tree-self \ + hdd-p16-group-tree-copy hdd-p17-tree-delete hdd-p17-tree-delete-cancel \ + hdd-p18-metadata hdd-p19-overwrite hdd-p19-policy diff --git a/applications/Volkov/README.md b/applications/Volkov/README.md new file mode 100644 index 0000000..b66dfad --- /dev/null +++ b/applications/Volkov/README.md @@ -0,0 +1,112 @@ +# Sprinter Commander + +Функциональный аналог классического двухпанельного Commander для Sprinter +Sp2000. Volkov Commander 4.05/4.99 и Dos Navigator используются как +поведенческие и архитектурные референсы; проект не является переносом их +DOS-кода. + +Текущее состояние: PoC 0.1 (этапы P1–P5) пройден в MAME / BIOS 3.06 / +DSS 1.71.57; аппаратный smoke-тест остаётся внешним контрольным пунктом. +Начат этап 0.2: готовы независимые для каждой панели сортировки по имени, +расширению, размеру и дате/времени в обоих направлениях, выбор записей через +`Insert`, `*` и маски `+`/`-`, создание каталога по `F7` и локальное +переименование файла/каталога по `F6` без overwrite, однообъектный F8 и +групповой рекурсивный F8 для выбранных файлов и каталогов, а также рекурсивный +F5 для выбранных файлов и каталогов. F5 сохраняет FAT date/time и атрибуты +`R/H/S/A` обычных файлов и поддерживает conflict-policy +`overwrite/skip/rename`, включая `overwrite all`/`skip all`, отдельную +защиту readonly и backup/rollback. Обход использует динамическую EMM-очередь. +Встроенный профиль имён уже различает белые каталоги, жёлтые `.EXE` и +светло-серые остальные файлы; загрузка пользовательских цветовых правил +остаётся задачей 1.0. +Командер +сканирует, хранит и сортирует каталоги, показывает явную виртуальную запись +предела DSS 1.71, безопасно копирует один файл через выделенную EMM-страницу +W3 и запускает `.EXE` с восстановлением режима, палитры, CWD и обеих панелей. + +## Сборка + +Из этого каталога: + +```sh +make +``` + +Результат: `build/sprcmd.exe`, конфигурация +`--memory big --safe --max-allocs 3000`. Значение `ALLOCS=3000` принято как +исходное для Commander и всех его проб; при диагностике его можно переопределить +явно, например `make ALLOCS=4000`. + +Основные тесты используют быстрый HDD `D:`: + +```sh +make hdd # только Commander +make hdd-stress # большой каталог P3 +make hdd-copy # набор успех/overwrite/cancel/zero для P4.1 +make hdd-exec # запуск child/restore/error для P4.2 +make hdd-p5 # 20 copy, граничные размеры, 100 refresh, lifecycle +make hdd-p5-enospc # реальный disk-full и cleanup temp +make hdd-p5-panels # точные границы панели 27/28 записей +make hdd-p5-copy-faults # детерминированная матрица ошибок copy/EXEC +make hdd-p5-viewer # запуск реального mdview2 и восстановление Commander +make hdd-p5-media # снятие/возврат hard2 во время F5 +make hdd-p6-sort # target-матрица и UI-сценарий сортировок 0.2 +make hdd-p7-select # выбор Insert/*, статистика и EMM cleanup +make hdd-p8-masks # тот же расширенный образ: редактор и маски +/− +make hdd-p9-mkdir # validator DOS 8.3, mkdir/error/CWD и UI F7 +make hdd-p10-rename # file/dir rename, no-overwrite, CWD и UI F6 +make hdd-p11-delete # confirm/cancel, file/empty-dir, guards и UI F8 +make hdd-p12-group-copy # legacy group fixture; current F5 also copies selected dirs +make hdd-p13-group-delete # legacy file-only group F8 regression +make hdd-p14-default-colors # built-in dir/EXE/file colours и restore палитры +make hdd-p15-tree-copy # recursive current directory, depth=16 и repeat guard +make hdd-p15-tree-cancel # Esc в 4-МБ файле и cleanup temp/traversal page +make hdd-p15-tree-self # target-inside-source guard до mutation +make hdd-p16-group-tree-copy # selected files + multiple directory roots +make hdd-p17-tree-delete # recursive group F8, depth=12, cancel confirmation +make hdd-p17-tree-delete-cancel # Esc после начала recursive group F8 +make hdd-p18-metadata # exact FAT date/time + R/H/S/A после F5 +make hdd-p19-overwrite # target backup/commit/rollback matrix +make hdd-p19-policy # UI overwrite/skip/rename/all/readonly/cancel +``` + +`make floppy` сохранён для smoke-проверки совместимости с FDD и не запускает +эмулятор. Подробнее: [политика тестовых носителей](docs/test-media.md). + +## Документы + +- [требования PoC](docs/commander-requirements.md); +- [архитектура](docs/commander-architecture.md); +- [roadmap](docs/commander-roadmap.md); +- [клавиши и команды](docs/commander-keymap.md); +- [результаты платформенных проб P1](docs/p1-platform-probes.md); +- [экранный backend и границы BIOS API](docs/p2-screen-backend.md); +- [результаты coordinate-screen probe P2.1](docs/p2-screen-probe-results.md); +- [результаты системной палитры P2.2](docs/p2-palette-results.md); +- [результаты двухпанельного skeleton P2](docs/p2-skeleton-results.md); +- [результаты файловых панелей P3](docs/p3-panel-results.md); +- [результаты копирования P4.1](docs/p4-copy-results.md); +- [результаты запуска EXE P4.2](docs/p4-exec-results.md); +- [результаты стабилизации P5](docs/p5-stability-results.md); +- [результаты сортировок 0.2/P6](docs/p6-sort-results.md); +- [результаты выбора записей 0.2/P7](docs/p7-selection-results.md); +- [результаты выбора по маске 0.2/P8](docs/p8-mask-results.md); +- [результаты создания каталога 0.2/P9](docs/p9-mkdir-results.md); +- [результаты локального rename 0.2/P10](docs/p10-rename-results.md); +- [результаты однообъектного delete 0.2/P11](docs/p11-delete-results.md); +- [результаты группового F5 0.2/P12](docs/p12-group-copy-results.md); +- [результаты группового F8 0.2/P13](docs/p13-group-delete-results.md); +- [результаты стандартных цветов P14](docs/p14-default-colors-results.md); +- [результаты рекурсивного F5/P15](docs/p15-tree-copy-results.md); +- [результаты рекурсивного group F5/P16](docs/p16-group-tree-copy-results.md); +- [результаты рекурсивного group F8/P17](docs/p17-tree-delete-results.md); +- [результаты сохранения метаданных F5/P18](docs/p18-metadata-results.md); +- [результаты conflict-policy F5/P19](docs/p19-copy-policy-results.md); +- [направление развития UI по образцу VC](docs/ui-reference.md); +- [инструкция и ограничения PoC 0.1](docs/poc-0.1-release.md); +- [носители интеграционных тестов](docs/test-media.md); +- [предел больших каталогов DSS и стратегия Commander](docs/dss-large-directories.md). + +Экран выводится системными координатными функциями ESTEX/BIOS без оконных +дескрипторов и без прямой адресации VRAM. Системные вызовы палитры разрешены; +их save/set/readback/restore и многоцветная сцена подтверждены пробой P2.2. diff --git a/applications/Volkov/artifacts/mame-p1/README.md b/applications/Volkov/artifacts/mame-p1/README.md new file mode 100644 index 0000000..e288d21 --- /dev/null +++ b/applications/Volkov/artifacts/mame-p1/README.md @@ -0,0 +1,36 @@ +# Артефакты MAME-прогона P1 + +Дата: 6 сентября 2026 года. + +Окружение: MAME 0.287 (`b0c4527c`), машина `sprinter`, BIOS `v3.06`, текущие системные +образы проекта. `p1probe.exe` и `p1child.exe` запускались с дискеты A:. + +Каталоги: + +- `directory/` — CWD, `*.*`, `*`, завершение F_NEXT с `ENOENT=3`; + `strict-enoent.png` снят после удаления временного допуска `EISDIR`; +- `emm/` — три страницы, граничные patterns и возврат free pages; +- `dskinfo/` — геометрия и свободное место текущего диска; +- `winrest/` — полный экран 80x32 и частичный вывод из offset `0x2000`; + `partial-phase-hidden.png`/`partial-stable.png` показывают неудачные raw- + атрибуты, а `partial-flash-phase.png`/`partial-contrast-stable.png` — + именованный цветной pattern. Повторная проверка декодированных RGB показала, + что два последних кадра пиксельно идентичны; P2.2 отдельно подтвердил + стабильность четырёх палитровых планов; +- `exec/` — оба вызова child и итоговые значения EXEC/WAIT; +- `exec-then-emm/` — повторный EMM-тест после двух EXEC в том же процессе; +- `keyboard-commander/` — навигация, F1–F10, Ctrl+R, Ctrl+PgUp, Alt+F1; +- `keyboard/` — дополнительная проба Ctrl+A, Alt+X и Shift+Tab. + +Нумерованные клавиатурные кадры соответствуют времени сценария: + +- `0000.png` — первые навигационные события; +- `0001.png` — продолжение навигации и начало F-клавиш; +- `0002.png` — полная базовая таблица до сочетаний; +- `0003.png` — сочетания, Esc и общий `RESULT: PASS`; +- `0004.png` — возврат в главное меню. + +Интерпретация результатов и ссылки на основные кадры находятся в +`docs/p1-platform-probes.md` и `docs/commander-keymap.md`. +Актуальная интерпретация палитры находится в +`docs/p2-palette-results.md`. diff --git a/applications/Volkov/artifacts/mame-p1/directory/menu.png b/applications/Volkov/artifacts/mame-p1/directory/menu.png new file mode 100644 index 0000000..ef16fc7 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/directory/menu.png differ diff --git a/applications/Volkov/artifacts/mame-p1/directory/result-early.png b/applications/Volkov/artifacts/mame-p1/directory/result-early.png new file mode 100644 index 0000000..34cef07 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/directory/result-early.png differ diff --git a/applications/Volkov/artifacts/mame-p1/directory/result.png b/applications/Volkov/artifacts/mame-p1/directory/result.png new file mode 100644 index 0000000..b0c2445 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/directory/result.png differ diff --git a/applications/Volkov/artifacts/mame-p1/directory/strict-enoent.png b/applications/Volkov/artifacts/mame-p1/directory/strict-enoent.png new file mode 100644 index 0000000..6d97dd1 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/directory/strict-enoent.png differ diff --git a/applications/Volkov/artifacts/mame-p1/directory/strict-menu.png b/applications/Volkov/artifacts/mame-p1/directory/strict-menu.png new file mode 100644 index 0000000..ef16fc7 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/directory/strict-menu.png differ diff --git a/applications/Volkov/artifacts/mame-p1/directory/transition.png b/applications/Volkov/artifacts/mame-p1/directory/transition.png new file mode 100644 index 0000000..b0c2445 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/directory/transition.png differ diff --git a/applications/Volkov/artifacts/mame-p1/dskinfo/menu.png b/applications/Volkov/artifacts/mame-p1/dskinfo/menu.png new file mode 100644 index 0000000..ef16fc7 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/dskinfo/menu.png differ diff --git a/applications/Volkov/artifacts/mame-p1/dskinfo/result.png b/applications/Volkov/artifacts/mame-p1/dskinfo/result.png new file mode 100644 index 0000000..b482e9e Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/dskinfo/result.png differ diff --git a/applications/Volkov/artifacts/mame-p1/dskinfo/transition.png b/applications/Volkov/artifacts/mame-p1/dskinfo/transition.png new file mode 100644 index 0000000..b482e9e Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/dskinfo/transition.png differ diff --git a/applications/Volkov/artifacts/mame-p1/emm/menu.png b/applications/Volkov/artifacts/mame-p1/emm/menu.png new file mode 100644 index 0000000..ef16fc7 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/emm/menu.png differ diff --git a/applications/Volkov/artifacts/mame-p1/emm/result.png b/applications/Volkov/artifacts/mame-p1/emm/result.png new file mode 100644 index 0000000..ef38cbf Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/emm/result.png differ diff --git a/applications/Volkov/artifacts/mame-p1/emm/transition.png b/applications/Volkov/artifacts/mame-p1/emm/transition.png new file mode 100644 index 0000000..ef38cbf Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/emm/transition.png differ diff --git a/applications/Volkov/artifacts/mame-p1/exec-then-emm/emm-result.png b/applications/Volkov/artifacts/mame-p1/exec-then-emm/emm-result.png new file mode 100644 index 0000000..ef38cbf Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/exec-then-emm/emm-result.png differ diff --git a/applications/Volkov/artifacts/mame-p1/exec-then-emm/exec-result.png b/applications/Volkov/artifacts/mame-p1/exec-then-emm/exec-result.png new file mode 100644 index 0000000..8ae8136 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/exec-then-emm/exec-result.png differ diff --git a/applications/Volkov/artifacts/mame-p1/exec-then-emm/menu.png b/applications/Volkov/artifacts/mame-p1/exec-then-emm/menu.png new file mode 100644 index 0000000..ef16fc7 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/exec-then-emm/menu.png differ diff --git a/applications/Volkov/artifacts/mame-p1/exec/case1-child.png b/applications/Volkov/artifacts/mame-p1/exec/case1-child.png new file mode 100644 index 0000000..0890c65 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/exec/case1-child.png differ diff --git a/applications/Volkov/artifacts/mame-p1/exec/case1-parent.png b/applications/Volkov/artifacts/mame-p1/exec/case1-parent.png new file mode 100644 index 0000000..c157572 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/exec/case1-parent.png differ diff --git a/applications/Volkov/artifacts/mame-p1/exec/case2-child.png b/applications/Volkov/artifacts/mame-p1/exec/case2-child.png new file mode 100644 index 0000000..0890c65 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/exec/case2-child.png differ diff --git a/applications/Volkov/artifacts/mame-p1/exec/case2-parent.png b/applications/Volkov/artifacts/mame-p1/exec/case2-parent.png new file mode 100644 index 0000000..7c0ce18 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/exec/case2-parent.png differ diff --git a/applications/Volkov/artifacts/mame-p1/exec/menu.png b/applications/Volkov/artifacts/mame-p1/exec/menu.png new file mode 100644 index 0000000..ef16fc7 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/exec/menu.png differ diff --git a/applications/Volkov/artifacts/mame-p1/exec/result.png b/applications/Volkov/artifacts/mame-p1/exec/result.png new file mode 100644 index 0000000..8ae8136 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/exec/result.png differ diff --git a/applications/Volkov/artifacts/mame-p1/keyboard-commander/sprinter/0000.png b/applications/Volkov/artifacts/mame-p1/keyboard-commander/sprinter/0000.png new file mode 100644 index 0000000..94ef339 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/keyboard-commander/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p1/keyboard-commander/sprinter/0001.png b/applications/Volkov/artifacts/mame-p1/keyboard-commander/sprinter/0001.png new file mode 100644 index 0000000..33e0c79 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/keyboard-commander/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p1/keyboard-commander/sprinter/0002.png b/applications/Volkov/artifacts/mame-p1/keyboard-commander/sprinter/0002.png new file mode 100644 index 0000000..6c3d05c Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/keyboard-commander/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p1/keyboard-commander/sprinter/0003.png b/applications/Volkov/artifacts/mame-p1/keyboard-commander/sprinter/0003.png new file mode 100644 index 0000000..b6595ce Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/keyboard-commander/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p1/keyboard-commander/sprinter/0004.png b/applications/Volkov/artifacts/mame-p1/keyboard-commander/sprinter/0004.png new file mode 100644 index 0000000..ef16fc7 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/keyboard-commander/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p1/keyboard/sprinter/0000.png b/applications/Volkov/artifacts/mame-p1/keyboard/sprinter/0000.png new file mode 100644 index 0000000..94ef339 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/keyboard/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p1/keyboard/sprinter/0001.png b/applications/Volkov/artifacts/mame-p1/keyboard/sprinter/0001.png new file mode 100644 index 0000000..33e0c79 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/keyboard/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p1/keyboard/sprinter/0002.png b/applications/Volkov/artifacts/mame-p1/keyboard/sprinter/0002.png new file mode 100644 index 0000000..a56ed41 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/keyboard/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p1/keyboard/sprinter/0003.png b/applications/Volkov/artifacts/mame-p1/keyboard/sprinter/0003.png new file mode 100644 index 0000000..294adb0 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/keyboard/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p1/keyboard/sprinter/0004.png b/applications/Volkov/artifacts/mame-p1/keyboard/sprinter/0004.png new file mode 100644 index 0000000..ef16fc7 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/keyboard/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p1/winrest/full-contrast.png b/applications/Volkov/artifacts/mame-p1/winrest/full-contrast.png new file mode 100644 index 0000000..8741942 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/winrest/full-contrast.png differ diff --git a/applications/Volkov/artifacts/mame-p1/winrest/full-current.png b/applications/Volkov/artifacts/mame-p1/winrest/full-current.png new file mode 100644 index 0000000..e052adc Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/winrest/full-current.png differ diff --git a/applications/Volkov/artifacts/mame-p1/winrest/full.png b/applications/Volkov/artifacts/mame-p1/winrest/full.png new file mode 100644 index 0000000..e052adc Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/winrest/full.png differ diff --git a/applications/Volkov/artifacts/mame-p1/winrest/menu-current.png b/applications/Volkov/artifacts/mame-p1/winrest/menu-current.png new file mode 100644 index 0000000..ef16fc7 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/winrest/menu-current.png differ diff --git a/applications/Volkov/artifacts/mame-p1/winrest/menu.png b/applications/Volkov/artifacts/mame-p1/winrest/menu.png new file mode 100644 index 0000000..ef16fc7 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/winrest/menu.png differ diff --git a/applications/Volkov/artifacts/mame-p1/winrest/partial-contrast-stable.png b/applications/Volkov/artifacts/mame-p1/winrest/partial-contrast-stable.png new file mode 100644 index 0000000..9ebdf88 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/winrest/partial-contrast-stable.png differ diff --git a/applications/Volkov/artifacts/mame-p1/winrest/partial-flash-phase.png b/applications/Volkov/artifacts/mame-p1/winrest/partial-flash-phase.png new file mode 100644 index 0000000..9ebdf88 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/winrest/partial-flash-phase.png differ diff --git a/applications/Volkov/artifacts/mame-p1/winrest/partial-phase-hidden.png b/applications/Volkov/artifacts/mame-p1/winrest/partial-phase-hidden.png new file mode 100644 index 0000000..c1df3d1 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/winrest/partial-phase-hidden.png differ diff --git a/applications/Volkov/artifacts/mame-p1/winrest/partial-stable.png b/applications/Volkov/artifacts/mame-p1/winrest/partial-stable.png new file mode 100644 index 0000000..c1df3d1 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/winrest/partial-stable.png differ diff --git a/applications/Volkov/artifacts/mame-p1/winrest/partial.png b/applications/Volkov/artifacts/mame-p1/winrest/partial.png new file mode 100644 index 0000000..c1df3d1 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/winrest/partial.png differ diff --git a/applications/Volkov/artifacts/mame-p1/winrest/result-contrast.png b/applications/Volkov/artifacts/mame-p1/winrest/result-contrast.png new file mode 100644 index 0000000..c07a8d0 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/winrest/result-contrast.png differ diff --git a/applications/Volkov/artifacts/mame-p1/winrest/result-current.png b/applications/Volkov/artifacts/mame-p1/winrest/result-current.png new file mode 100644 index 0000000..c07a8d0 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/winrest/result-current.png differ diff --git a/applications/Volkov/artifacts/mame-p1/winrest/result.png b/applications/Volkov/artifacts/mame-p1/winrest/result.png new file mode 100644 index 0000000..c07a8d0 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p1/winrest/result.png differ diff --git a/applications/Volkov/artifacts/mame-p10/rename-final/sprinter/0000.png b/applications/Volkov/artifacts/mame-p10/rename-final/sprinter/0000.png new file mode 100644 index 0000000..e99a71a Binary files /dev/null and b/applications/Volkov/artifacts/mame-p10/rename-final/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p10/rename-final/sprinter/0001.png b/applications/Volkov/artifacts/mame-p10/rename-final/sprinter/0001.png new file mode 100644 index 0000000..f3d4d23 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p10/rename-final/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p10/rename-final/sprinter/0002.png b/applications/Volkov/artifacts/mame-p10/rename-final/sprinter/0002.png new file mode 100644 index 0000000..1b49f0b Binary files /dev/null and b/applications/Volkov/artifacts/mame-p10/rename-final/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p10/rename-final/sprinter/0003.png b/applications/Volkov/artifacts/mame-p10/rename-final/sprinter/0003.png new file mode 100644 index 0000000..6ef9f1a Binary files /dev/null and b/applications/Volkov/artifacts/mame-p10/rename-final/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p10/rename-final/sprinter/0004.png b/applications/Volkov/artifacts/mame-p10/rename-final/sprinter/0004.png new file mode 100644 index 0000000..2d9d34e Binary files /dev/null and b/applications/Volkov/artifacts/mame-p10/rename-final/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p10/rename-final/sprinter/0005.png b/applications/Volkov/artifacts/mame-p10/rename-final/sprinter/0005.png new file mode 100644 index 0000000..ab02a2e Binary files /dev/null and b/applications/Volkov/artifacts/mame-p10/rename-final/sprinter/0005.png differ diff --git a/applications/Volkov/artifacts/mame-p10/rename-final/sprinter/0006.png b/applications/Volkov/artifacts/mame-p10/rename-final/sprinter/0006.png new file mode 100644 index 0000000..4526c99 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p10/rename-final/sprinter/0006.png differ diff --git a/applications/Volkov/artifacts/mame-p10/rename-final/sprinter/0007.png b/applications/Volkov/artifacts/mame-p10/rename-final/sprinter/0007.png new file mode 100644 index 0000000..a56f301 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p10/rename-final/sprinter/0007.png differ diff --git a/applications/Volkov/artifacts/mame-p10/rename-pass/sprinter/0000.png b/applications/Volkov/artifacts/mame-p10/rename-pass/sprinter/0000.png new file mode 100644 index 0000000..e99a71a Binary files /dev/null and b/applications/Volkov/artifacts/mame-p10/rename-pass/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p10/rename-pass/sprinter/0001.png b/applications/Volkov/artifacts/mame-p10/rename-pass/sprinter/0001.png new file mode 100644 index 0000000..894df1d Binary files /dev/null and b/applications/Volkov/artifacts/mame-p10/rename-pass/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p10/rename-pass/sprinter/0002.png b/applications/Volkov/artifacts/mame-p10/rename-pass/sprinter/0002.png new file mode 100644 index 0000000..54c654f Binary files /dev/null and b/applications/Volkov/artifacts/mame-p10/rename-pass/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p10/rename-pass/sprinter/0003.png b/applications/Volkov/artifacts/mame-p10/rename-pass/sprinter/0003.png new file mode 100644 index 0000000..947106b Binary files /dev/null and b/applications/Volkov/artifacts/mame-p10/rename-pass/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p10/rename-pass/sprinter/0004.png b/applications/Volkov/artifacts/mame-p10/rename-pass/sprinter/0004.png new file mode 100644 index 0000000..76c6f0d Binary files /dev/null and b/applications/Volkov/artifacts/mame-p10/rename-pass/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p10/rename-pass/sprinter/0005.png b/applications/Volkov/artifacts/mame-p10/rename-pass/sprinter/0005.png new file mode 100644 index 0000000..8b1adde Binary files /dev/null and b/applications/Volkov/artifacts/mame-p10/rename-pass/sprinter/0005.png differ diff --git a/applications/Volkov/artifacts/mame-p10/rename-pass/sprinter/0006.png b/applications/Volkov/artifacts/mame-p10/rename-pass/sprinter/0006.png new file mode 100644 index 0000000..afb278c Binary files /dev/null and b/applications/Volkov/artifacts/mame-p10/rename-pass/sprinter/0006.png differ diff --git a/applications/Volkov/artifacts/mame-p10/rename-pass/sprinter/0007.png b/applications/Volkov/artifacts/mame-p10/rename-pass/sprinter/0007.png new file mode 100644 index 0000000..a56f301 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p10/rename-pass/sprinter/0007.png differ diff --git a/applications/Volkov/artifacts/mame-p10/rename-run/sprinter/0000.png b/applications/Volkov/artifacts/mame-p10/rename-run/sprinter/0000.png new file mode 100644 index 0000000..e99a71a Binary files /dev/null and b/applications/Volkov/artifacts/mame-p10/rename-run/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p10/rename-run/sprinter/0001.png b/applications/Volkov/artifacts/mame-p10/rename-run/sprinter/0001.png new file mode 100644 index 0000000..c16fe10 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p10/rename-run/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p10/rename-run/sprinter/0002.png b/applications/Volkov/artifacts/mame-p10/rename-run/sprinter/0002.png new file mode 100644 index 0000000..754d671 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p10/rename-run/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p10/rename-run/sprinter/0003.png b/applications/Volkov/artifacts/mame-p10/rename-run/sprinter/0003.png new file mode 100644 index 0000000..522a10d Binary files /dev/null and b/applications/Volkov/artifacts/mame-p10/rename-run/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p10/rename-run/sprinter/0004.png b/applications/Volkov/artifacts/mame-p10/rename-run/sprinter/0004.png new file mode 100644 index 0000000..e2da05e Binary files /dev/null and b/applications/Volkov/artifacts/mame-p10/rename-run/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p10/rename-run/sprinter/0005.png b/applications/Volkov/artifacts/mame-p10/rename-run/sprinter/0005.png new file mode 100644 index 0000000..8b1adde Binary files /dev/null and b/applications/Volkov/artifacts/mame-p10/rename-run/sprinter/0005.png differ diff --git a/applications/Volkov/artifacts/mame-p10/rename-run/sprinter/0006.png b/applications/Volkov/artifacts/mame-p10/rename-run/sprinter/0006.png new file mode 100644 index 0000000..06d6e0f Binary files /dev/null and b/applications/Volkov/artifacts/mame-p10/rename-run/sprinter/0006.png differ diff --git a/applications/Volkov/artifacts/mame-p10/rename-run/sprinter/0007.png b/applications/Volkov/artifacts/mame-p10/rename-run/sprinter/0007.png new file mode 100644 index 0000000..a56f301 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p10/rename-run/sprinter/0007.png differ diff --git a/applications/Volkov/artifacts/mame-p10/rename/clean-dss-return.png b/applications/Volkov/artifacts/mame-p10/rename/clean-dss-return.png new file mode 100644 index 0000000..a56f301 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p10/rename/clean-dss-return.png differ diff --git a/applications/Volkov/artifacts/mame-p10/rename/collision.png b/applications/Volkov/artifacts/mame-p10/rename/collision.png new file mode 100644 index 0000000..2d9d34e Binary files /dev/null and b/applications/Volkov/artifacts/mame-p10/rename/collision.png differ diff --git a/applications/Volkov/artifacts/mame-p10/rename/dialog-default.png b/applications/Volkov/artifacts/mame-p10/rename/dialog-default.png new file mode 100644 index 0000000..f3d4d23 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p10/rename/dialog-default.png differ diff --git a/applications/Volkov/artifacts/mame-p10/rename/group-guard.png b/applications/Volkov/artifacts/mame-p10/rename/group-guard.png new file mode 100644 index 0000000..4526c99 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p10/rename/group-guard.png differ diff --git a/applications/Volkov/artifacts/mame-p10/rename/renamed-focused.png b/applications/Volkov/artifacts/mame-p10/rename/renamed-focused.png new file mode 100644 index 0000000..1b49f0b Binary files /dev/null and b/applications/Volkov/artifacts/mame-p10/rename/renamed-focused.png differ diff --git a/applications/Volkov/artifacts/mame-p10/rename/unchanged-name.png b/applications/Volkov/artifacts/mame-p10/rename/unchanged-name.png new file mode 100644 index 0000000..6ef9f1a Binary files /dev/null and b/applications/Volkov/artifacts/mame-p10/rename/unchanged-name.png differ diff --git a/applications/Volkov/artifacts/mame-p10/rename/virtual-guard.png b/applications/Volkov/artifacts/mame-p10/rename/virtual-guard.png new file mode 100644 index 0000000..ab02a2e Binary files /dev/null and b/applications/Volkov/artifacts/mame-p10/rename/virtual-guard.png differ diff --git a/applications/Volkov/artifacts/mame-p11/delete-run/sprinter/0000.png b/applications/Volkov/artifacts/mame-p11/delete-run/sprinter/0000.png new file mode 100644 index 0000000..0d47c78 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p11/delete-run/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p11/delete-run/sprinter/0001.png b/applications/Volkov/artifacts/mame-p11/delete-run/sprinter/0001.png new file mode 100644 index 0000000..df2c5b5 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p11/delete-run/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p11/delete-run/sprinter/0002.png b/applications/Volkov/artifacts/mame-p11/delete-run/sprinter/0002.png new file mode 100644 index 0000000..b04d5cb Binary files /dev/null and b/applications/Volkov/artifacts/mame-p11/delete-run/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p11/delete-run/sprinter/0003.png b/applications/Volkov/artifacts/mame-p11/delete-run/sprinter/0003.png new file mode 100644 index 0000000..e23d66b Binary files /dev/null and b/applications/Volkov/artifacts/mame-p11/delete-run/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p11/delete-run/sprinter/0004.png b/applications/Volkov/artifacts/mame-p11/delete-run/sprinter/0004.png new file mode 100644 index 0000000..461f913 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p11/delete-run/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p11/delete-run/sprinter/0005.png b/applications/Volkov/artifacts/mame-p11/delete-run/sprinter/0005.png new file mode 100644 index 0000000..bbecd05 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p11/delete-run/sprinter/0005.png differ diff --git a/applications/Volkov/artifacts/mame-p11/delete-run/sprinter/0006.png b/applications/Volkov/artifacts/mame-p11/delete-run/sprinter/0006.png new file mode 100644 index 0000000..df2f531 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p11/delete-run/sprinter/0006.png differ diff --git a/applications/Volkov/artifacts/mame-p11/delete-run/sprinter/0007.png b/applications/Volkov/artifacts/mame-p11/delete-run/sprinter/0007.png new file mode 100644 index 0000000..4c8acbe Binary files /dev/null and b/applications/Volkov/artifacts/mame-p11/delete-run/sprinter/0007.png differ diff --git a/applications/Volkov/artifacts/mame-p11/delete-run/sprinter/0008.png b/applications/Volkov/artifacts/mame-p11/delete-run/sprinter/0008.png new file mode 100644 index 0000000..a56f301 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p11/delete-run/sprinter/0008.png differ diff --git a/applications/Volkov/artifacts/mame-p11/delete/cancel-preserves.png b/applications/Volkov/artifacts/mame-p11/delete/cancel-preserves.png new file mode 100644 index 0000000..b04d5cb Binary files /dev/null and b/applications/Volkov/artifacts/mame-p11/delete/cancel-preserves.png differ diff --git a/applications/Volkov/artifacts/mame-p11/delete/clean-dss-return.png b/applications/Volkov/artifacts/mame-p11/delete/clean-dss-return.png new file mode 100644 index 0000000..a56f301 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p11/delete/clean-dss-return.png differ diff --git a/applications/Volkov/artifacts/mame-p11/delete/empty-dir-deleted.png b/applications/Volkov/artifacts/mame-p11/delete/empty-dir-deleted.png new file mode 100644 index 0000000..461f913 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p11/delete/empty-dir-deleted.png differ diff --git a/applications/Volkov/artifacts/mame-p11/delete/file-confirm.png b/applications/Volkov/artifacts/mame-p11/delete/file-confirm.png new file mode 100644 index 0000000..df2c5b5 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p11/delete/file-confirm.png differ diff --git a/applications/Volkov/artifacts/mame-p11/delete/file-deleted.png b/applications/Volkov/artifacts/mame-p11/delete/file-deleted.png new file mode 100644 index 0000000..e23d66b Binary files /dev/null and b/applications/Volkov/artifacts/mame-p11/delete/file-deleted.png differ diff --git a/applications/Volkov/artifacts/mame-p11/delete/group-guard.png b/applications/Volkov/artifacts/mame-p11/delete/group-guard.png new file mode 100644 index 0000000..4c8acbe Binary files /dev/null and b/applications/Volkov/artifacts/mame-p11/delete/group-guard.png differ diff --git a/applications/Volkov/artifacts/mame-p11/delete/nonempty-preserved.png b/applications/Volkov/artifacts/mame-p11/delete/nonempty-preserved.png new file mode 100644 index 0000000..bbecd05 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p11/delete/nonempty-preserved.png differ diff --git a/applications/Volkov/artifacts/mame-p11/delete/virtual-guard.png b/applications/Volkov/artifacts/mame-p11/delete/virtual-guard.png new file mode 100644 index 0000000..df2f531 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p11/delete/virtual-guard.png differ diff --git a/applications/Volkov/artifacts/mame-p12/group-copy-p16-regression/sprinter/0000.png b/applications/Volkov/artifacts/mame-p12/group-copy-p16-regression/sprinter/0000.png new file mode 100644 index 0000000..382c532 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p12/group-copy-p16-regression/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p12/group-copy-p16-regression/sprinter/0001.png b/applications/Volkov/artifacts/mame-p12/group-copy-p16-regression/sprinter/0001.png new file mode 100644 index 0000000..03e8fd3 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p12/group-copy-p16-regression/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p12/group-copy-p16-regression/sprinter/0002.png b/applications/Volkov/artifacts/mame-p12/group-copy-p16-regression/sprinter/0002.png new file mode 100644 index 0000000..3fe3807 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p12/group-copy-p16-regression/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p12/group-copy-p16-regression/sprinter/0003.png b/applications/Volkov/artifacts/mame-p12/group-copy-p16-regression/sprinter/0003.png new file mode 100644 index 0000000..612df0a Binary files /dev/null and b/applications/Volkov/artifacts/mame-p12/group-copy-p16-regression/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p12/group-copy-p16-regression/sprinter/0004.png b/applications/Volkov/artifacts/mame-p12/group-copy-p16-regression/sprinter/0004.png new file mode 100644 index 0000000..12240d5 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p12/group-copy-p16-regression/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p12/group-copy-p16-regression/sprinter/0005.png b/applications/Volkov/artifacts/mame-p12/group-copy-p16-regression/sprinter/0005.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p12/group-copy-p16-regression/sprinter/0005.png differ diff --git a/applications/Volkov/artifacts/mame-p12/group-copy-run/sprinter/0000.png b/applications/Volkov/artifacts/mame-p12/group-copy-run/sprinter/0000.png new file mode 100644 index 0000000..d685749 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p12/group-copy-run/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p12/group-copy-run/sprinter/0001.png b/applications/Volkov/artifacts/mame-p12/group-copy-run/sprinter/0001.png new file mode 100644 index 0000000..8dfc702 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p12/group-copy-run/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p12/group-copy-run/sprinter/0002.png b/applications/Volkov/artifacts/mame-p12/group-copy-run/sprinter/0002.png new file mode 100644 index 0000000..f271128 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p12/group-copy-run/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p12/group-copy-run/sprinter/0003.png b/applications/Volkov/artifacts/mame-p12/group-copy-run/sprinter/0003.png new file mode 100644 index 0000000..c7b537b Binary files /dev/null and b/applications/Volkov/artifacts/mame-p12/group-copy-run/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p12/group-copy-run/sprinter/0004.png b/applications/Volkov/artifacts/mame-p12/group-copy-run/sprinter/0004.png new file mode 100644 index 0000000..df0c174 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p12/group-copy-run/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p12/group-copy-run/sprinter/0005.png b/applications/Volkov/artifacts/mame-p12/group-copy-run/sprinter/0005.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p12/group-copy-run/sprinter/0005.png differ diff --git a/applications/Volkov/artifacts/mame-p12/group-copy/clean-dss-return.png b/applications/Volkov/artifacts/mame-p12/group-copy/clean-dss-return.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p12/group-copy/clean-dss-return.png differ diff --git a/applications/Volkov/artifacts/mame-p12/group-copy/copied-and-skipped-dir.png b/applications/Volkov/artifacts/mame-p12/group-copy/copied-and-skipped-dir.png new file mode 100644 index 0000000..f271128 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p12/group-copy/copied-and-skipped-dir.png differ diff --git a/applications/Volkov/artifacts/mame-p12/group-copy/passive-refreshed.png b/applications/Volkov/artifacts/mame-p12/group-copy/passive-refreshed.png new file mode 100644 index 0000000..df0c174 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p12/group-copy/passive-refreshed.png differ diff --git a/applications/Volkov/artifacts/mame-p12/group-copy/repeat-stops-eexist.png b/applications/Volkov/artifacts/mame-p12/group-copy/repeat-stops-eexist.png new file mode 100644 index 0000000..c7b537b Binary files /dev/null and b/applications/Volkov/artifacts/mame-p12/group-copy/repeat-stops-eexist.png differ diff --git a/applications/Volkov/artifacts/mame-p12/group-copy/selected-files-and-dir.png b/applications/Volkov/artifacts/mame-p12/group-copy/selected-files-and-dir.png new file mode 100644 index 0000000..8dfc702 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p12/group-copy/selected-files-and-dir.png differ diff --git a/applications/Volkov/artifacts/mame-p12/single-copy-regression/sprinter/0000.png b/applications/Volkov/artifacts/mame-p12/single-copy-regression/sprinter/0000.png new file mode 100644 index 0000000..cca9dcf Binary files /dev/null and b/applications/Volkov/artifacts/mame-p12/single-copy-regression/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p12/single-copy-regression/sprinter/0001.png b/applications/Volkov/artifacts/mame-p12/single-copy-regression/sprinter/0001.png new file mode 100644 index 0000000..362fbe8 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p12/single-copy-regression/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p12/single-copy-regression/sprinter/0002.png b/applications/Volkov/artifacts/mame-p12/single-copy-regression/sprinter/0002.png new file mode 100644 index 0000000..7a9a26d Binary files /dev/null and b/applications/Volkov/artifacts/mame-p12/single-copy-regression/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p12/single-copy-regression/sprinter/0003.png b/applications/Volkov/artifacts/mame-p12/single-copy-regression/sprinter/0003.png new file mode 100644 index 0000000..ee538ad Binary files /dev/null and b/applications/Volkov/artifacts/mame-p12/single-copy-regression/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p12/single-copy-regression/sprinter/0004.png b/applications/Volkov/artifacts/mame-p12/single-copy-regression/sprinter/0004.png new file mode 100644 index 0000000..42d8c2f Binary files /dev/null and b/applications/Volkov/artifacts/mame-p12/single-copy-regression/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p12/single-copy-regression/sprinter/0005.png b/applications/Volkov/artifacts/mame-p12/single-copy-regression/sprinter/0005.png new file mode 100644 index 0000000..191d7dd Binary files /dev/null and b/applications/Volkov/artifacts/mame-p12/single-copy-regression/sprinter/0005.png differ diff --git a/applications/Volkov/artifacts/mame-p12/single-copy-regression/sprinter/0006.png b/applications/Volkov/artifacts/mame-p12/single-copy-regression/sprinter/0006.png new file mode 100644 index 0000000..5e501dc Binary files /dev/null and b/applications/Volkov/artifacts/mame-p12/single-copy-regression/sprinter/0006.png differ diff --git a/applications/Volkov/artifacts/mame-p12/single-copy-regression/sprinter/0007.png b/applications/Volkov/artifacts/mame-p12/single-copy-regression/sprinter/0007.png new file mode 100644 index 0000000..7dde71d Binary files /dev/null and b/applications/Volkov/artifacts/mame-p12/single-copy-regression/sprinter/0007.png differ diff --git a/applications/Volkov/artifacts/mame-p12/single-copy-regression/sprinter/0008.png b/applications/Volkov/artifacts/mame-p12/single-copy-regression/sprinter/0008.png new file mode 100644 index 0000000..34dc9b3 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p12/single-copy-regression/sprinter/0008.png differ diff --git a/applications/Volkov/artifacts/mame-p12/single-copy-regression/sprinter/0009.png b/applications/Volkov/artifacts/mame-p12/single-copy-regression/sprinter/0009.png new file mode 100644 index 0000000..b31a79c Binary files /dev/null and b/applications/Volkov/artifacts/mame-p12/single-copy-regression/sprinter/0009.png differ diff --git a/applications/Volkov/artifacts/mame-p12/single-copy-regression/sprinter/0010.png b/applications/Volkov/artifacts/mame-p12/single-copy-regression/sprinter/0010.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p12/single-copy-regression/sprinter/0010.png differ diff --git a/applications/Volkov/artifacts/mame-p13/file-only-regression/before.png b/applications/Volkov/artifacts/mame-p13/file-only-regression/before.png new file mode 100644 index 0000000..7305a80 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p13/file-only-regression/before.png differ diff --git a/applications/Volkov/artifacts/mame-p13/file-only-regression/cancel-preserves.png b/applications/Volkov/artifacts/mame-p13/file-only-regression/cancel-preserves.png new file mode 100644 index 0000000..81f988a Binary files /dev/null and b/applications/Volkov/artifacts/mame-p13/file-only-regression/cancel-preserves.png differ diff --git a/applications/Volkov/artifacts/mame-p13/file-only-regression/clean-dss-return.png b/applications/Volkov/artifacts/mame-p13/file-only-regression/clean-dss-return.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p13/file-only-regression/clean-dss-return.png differ diff --git a/applications/Volkov/artifacts/mame-p13/file-only-regression/files-deleted.png b/applications/Volkov/artifacts/mame-p13/file-only-regression/files-deleted.png new file mode 100644 index 0000000..d2c6ff9 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p13/file-only-regression/files-deleted.png differ diff --git a/applications/Volkov/artifacts/mame-p13/file-only-regression/group-confirm.png b/applications/Volkov/artifacts/mame-p13/file-only-regression/group-confirm.png new file mode 100644 index 0000000..e7584a5 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p13/file-only-regression/group-confirm.png differ diff --git a/applications/Volkov/artifacts/mame-p13/file-only-regression/selected-files.png b/applications/Volkov/artifacts/mame-p13/file-only-regression/selected-files.png new file mode 100644 index 0000000..dcc3d47 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p13/file-only-regression/selected-files.png differ diff --git a/applications/Volkov/artifacts/mame-p13/group-delete-rerun/sprinter/0000.png b/applications/Volkov/artifacts/mame-p13/group-delete-rerun/sprinter/0000.png new file mode 100644 index 0000000..5d69c55 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p13/group-delete-rerun/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p13/group-delete-rerun/sprinter/0001.png b/applications/Volkov/artifacts/mame-p13/group-delete-rerun/sprinter/0001.png new file mode 100644 index 0000000..865a6cf Binary files /dev/null and b/applications/Volkov/artifacts/mame-p13/group-delete-rerun/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p13/group-delete-rerun/sprinter/0002.png b/applications/Volkov/artifacts/mame-p13/group-delete-rerun/sprinter/0002.png new file mode 100644 index 0000000..12501de Binary files /dev/null and b/applications/Volkov/artifacts/mame-p13/group-delete-rerun/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p13/group-delete-rerun/sprinter/0003.png b/applications/Volkov/artifacts/mame-p13/group-delete-rerun/sprinter/0003.png new file mode 100644 index 0000000..9a2f5a4 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p13/group-delete-rerun/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p13/group-delete-rerun/sprinter/0004.png b/applications/Volkov/artifacts/mame-p13/group-delete-rerun/sprinter/0004.png new file mode 100644 index 0000000..ce3d3f2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p13/group-delete-rerun/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p13/group-delete-rerun/sprinter/0005.png b/applications/Volkov/artifacts/mame-p13/group-delete-rerun/sprinter/0005.png new file mode 100644 index 0000000..7745055 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p13/group-delete-rerun/sprinter/0005.png differ diff --git a/applications/Volkov/artifacts/mame-p13/group-delete-rerun/sprinter/0006.png b/applications/Volkov/artifacts/mame-p13/group-delete-rerun/sprinter/0006.png new file mode 100644 index 0000000..a56f301 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p13/group-delete-rerun/sprinter/0006.png differ diff --git a/applications/Volkov/artifacts/mame-p13/group-delete-run/sprinter/0000.png b/applications/Volkov/artifacts/mame-p13/group-delete-run/sprinter/0000.png new file mode 100644 index 0000000..5d69c55 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p13/group-delete-run/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p13/group-delete-run/sprinter/0001.png b/applications/Volkov/artifacts/mame-p13/group-delete-run/sprinter/0001.png new file mode 100644 index 0000000..ad71e67 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p13/group-delete-run/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p13/group-delete-run/sprinter/0002.png b/applications/Volkov/artifacts/mame-p13/group-delete-run/sprinter/0002.png new file mode 100644 index 0000000..8425c0c Binary files /dev/null and b/applications/Volkov/artifacts/mame-p13/group-delete-run/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p13/group-delete-run/sprinter/0003.png b/applications/Volkov/artifacts/mame-p13/group-delete-run/sprinter/0003.png new file mode 100644 index 0000000..5250617 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p13/group-delete-run/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p13/group-delete-run/sprinter/0004.png b/applications/Volkov/artifacts/mame-p13/group-delete-run/sprinter/0004.png new file mode 100644 index 0000000..7e3cf69 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p13/group-delete-run/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p13/group-delete-run/sprinter/0005.png b/applications/Volkov/artifacts/mame-p13/group-delete-run/sprinter/0005.png new file mode 100644 index 0000000..a83a2ad Binary files /dev/null and b/applications/Volkov/artifacts/mame-p13/group-delete-run/sprinter/0005.png differ diff --git a/applications/Volkov/artifacts/mame-p13/group-delete-run/sprinter/0006.png b/applications/Volkov/artifacts/mame-p13/group-delete-run/sprinter/0006.png new file mode 100644 index 0000000..da56269 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p13/group-delete-run/sprinter/0006.png differ diff --git a/applications/Volkov/artifacts/mame-p13/group-delete/cancel-preserves.png b/applications/Volkov/artifacts/mame-p13/group-delete/cancel-preserves.png new file mode 100644 index 0000000..9a2f5a4 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p13/group-delete/cancel-preserves.png differ diff --git a/applications/Volkov/artifacts/mame-p13/group-delete/clean-dss-return.png b/applications/Volkov/artifacts/mame-p13/group-delete/clean-dss-return.png new file mode 100644 index 0000000..a56f301 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p13/group-delete/clean-dss-return.png differ diff --git a/applications/Volkov/artifacts/mame-p13/group-delete/directory-only-refused.png b/applications/Volkov/artifacts/mame-p13/group-delete/directory-only-refused.png new file mode 100644 index 0000000..7745055 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p13/group-delete/directory-only-refused.png differ diff --git a/applications/Volkov/artifacts/mame-p13/group-delete/files-deleted-dirs-preserved.png b/applications/Volkov/artifacts/mame-p13/group-delete/files-deleted-dirs-preserved.png new file mode 100644 index 0000000..ce3d3f2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p13/group-delete/files-deleted-dirs-preserved.png differ diff --git a/applications/Volkov/artifacts/mame-p13/group-delete/group-confirm.png b/applications/Volkov/artifacts/mame-p13/group-delete/group-confirm.png new file mode 100644 index 0000000..12501de Binary files /dev/null and b/applications/Volkov/artifacts/mame-p13/group-delete/group-confirm.png differ diff --git a/applications/Volkov/artifacts/mame-p13/group-delete/selected-files-and-dirs.png b/applications/Volkov/artifacts/mame-p13/group-delete/selected-files-and-dirs.png new file mode 100644 index 0000000..865a6cf Binary files /dev/null and b/applications/Volkov/artifacts/mame-p13/group-delete/selected-files-and-dirs.png differ diff --git a/applications/Volkov/artifacts/mame-p14/default-colors-run/sprinter/0000.png b/applications/Volkov/artifacts/mame-p14/default-colors-run/sprinter/0000.png new file mode 100644 index 0000000..b018d31 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p14/default-colors-run/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p14/default-colors-run/sprinter/0001.png b/applications/Volkov/artifacts/mame-p14/default-colors-run/sprinter/0001.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p14/default-colors-run/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p14/default-colors/clean-dss-palette.png b/applications/Volkov/artifacts/mame-p14/default-colors/clean-dss-palette.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p14/default-colors/clean-dss-palette.png differ diff --git a/applications/Volkov/artifacts/mame-p14/default-colors/file-types.png b/applications/Volkov/artifacts/mame-p14/default-colors/file-types.png new file mode 100644 index 0000000..b018d31 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p14/default-colors/file-types.png differ diff --git a/applications/Volkov/artifacts/mame-p15/tree-cancel-run/sprinter/0000.png b/applications/Volkov/artifacts/mame-p15/tree-cancel-run/sprinter/0000.png new file mode 100644 index 0000000..dcb026f Binary files /dev/null and b/applications/Volkov/artifacts/mame-p15/tree-cancel-run/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p15/tree-cancel-run/sprinter/0001.png b/applications/Volkov/artifacts/mame-p15/tree-cancel-run/sprinter/0001.png new file mode 100644 index 0000000..dc0d6c6 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p15/tree-cancel-run/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p15/tree-cancel-run/sprinter/0002.png b/applications/Volkov/artifacts/mame-p15/tree-cancel-run/sprinter/0002.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p15/tree-cancel-run/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p15/tree-copy-run/sprinter/0000.png b/applications/Volkov/artifacts/mame-p15/tree-copy-run/sprinter/0000.png new file mode 100644 index 0000000..984a375 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p15/tree-copy-run/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p15/tree-copy-run/sprinter/0001.png b/applications/Volkov/artifacts/mame-p15/tree-copy-run/sprinter/0001.png new file mode 100644 index 0000000..b012944 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p15/tree-copy-run/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p15/tree-copy-run/sprinter/0002.png b/applications/Volkov/artifacts/mame-p15/tree-copy-run/sprinter/0002.png new file mode 100644 index 0000000..edead95 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p15/tree-copy-run/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p15/tree-copy-run/sprinter/0003.png b/applications/Volkov/artifacts/mame-p15/tree-copy-run/sprinter/0003.png new file mode 100644 index 0000000..3bbbd69 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p15/tree-copy-run/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p15/tree-copy-run/sprinter/0004.png b/applications/Volkov/artifacts/mame-p15/tree-copy-run/sprinter/0004.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p15/tree-copy-run/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p15/tree-copy/before.png b/applications/Volkov/artifacts/mame-p15/tree-copy/before.png new file mode 100644 index 0000000..984a375 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p15/tree-copy/before.png differ diff --git a/applications/Volkov/artifacts/mame-p15/tree-copy/cancel-clean.png b/applications/Volkov/artifacts/mame-p15/tree-copy/cancel-clean.png new file mode 100644 index 0000000..dc0d6c6 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p15/tree-copy/cancel-clean.png differ diff --git a/applications/Volkov/artifacts/mame-p15/tree-copy/clean-dss-return.png b/applications/Volkov/artifacts/mame-p15/tree-copy/clean-dss-return.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p15/tree-copy/clean-dss-return.png differ diff --git a/applications/Volkov/artifacts/mame-p15/tree-copy/passive-refreshed.png b/applications/Volkov/artifacts/mame-p15/tree-copy/passive-refreshed.png new file mode 100644 index 0000000..3bbbd69 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p15/tree-copy/passive-refreshed.png differ diff --git a/applications/Volkov/artifacts/mame-p15/tree-copy/repeat-existing-target.png b/applications/Volkov/artifacts/mame-p15/tree-copy/repeat-existing-target.png new file mode 100644 index 0000000..edead95 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p15/tree-copy/repeat-existing-target.png differ diff --git a/applications/Volkov/artifacts/mame-p15/tree-copy/self-target-refused.png b/applications/Volkov/artifacts/mame-p15/tree-copy/self-target-refused.png new file mode 100644 index 0000000..f2497af Binary files /dev/null and b/applications/Volkov/artifacts/mame-p15/tree-copy/self-target-refused.png differ diff --git a/applications/Volkov/artifacts/mame-p15/tree-copy/success.png b/applications/Volkov/artifacts/mame-p15/tree-copy/success.png new file mode 100644 index 0000000..b012944 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p15/tree-copy/success.png differ diff --git a/applications/Volkov/artifacts/mame-p15/tree-self-run/sprinter/0000.png b/applications/Volkov/artifacts/mame-p15/tree-self-run/sprinter/0000.png new file mode 100644 index 0000000..edd4c1a Binary files /dev/null and b/applications/Volkov/artifacts/mame-p15/tree-self-run/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p15/tree-self-run/sprinter/0001.png b/applications/Volkov/artifacts/mame-p15/tree-self-run/sprinter/0001.png new file mode 100644 index 0000000..f2497af Binary files /dev/null and b/applications/Volkov/artifacts/mame-p15/tree-self-run/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p15/tree-self-run/sprinter/0002.png b/applications/Volkov/artifacts/mame-p15/tree-self-run/sprinter/0002.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p15/tree-self-run/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p16/group-tree-run/sprinter/0000.png b/applications/Volkov/artifacts/mame-p16/group-tree-run/sprinter/0000.png new file mode 100644 index 0000000..eec5d44 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p16/group-tree-run/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p16/group-tree-run/sprinter/0001.png b/applications/Volkov/artifacts/mame-p16/group-tree-run/sprinter/0001.png new file mode 100644 index 0000000..3d805e9 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p16/group-tree-run/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p16/group-tree-run/sprinter/0002.png b/applications/Volkov/artifacts/mame-p16/group-tree-run/sprinter/0002.png new file mode 100644 index 0000000..451d1d0 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p16/group-tree-run/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p16/group-tree-run/sprinter/0003.png b/applications/Volkov/artifacts/mame-p16/group-tree-run/sprinter/0003.png new file mode 100644 index 0000000..a56f301 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p16/group-tree-run/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p16/group-tree/clean-dss-return.png b/applications/Volkov/artifacts/mame-p16/group-tree/clean-dss-return.png new file mode 100644 index 0000000..a56f301 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p16/group-tree/clean-dss-return.png differ diff --git a/applications/Volkov/artifacts/mame-p16/group-tree/copied.png b/applications/Volkov/artifacts/mame-p16/group-tree/copied.png new file mode 100644 index 0000000..3d805e9 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p16/group-tree/copied.png differ diff --git a/applications/Volkov/artifacts/mame-p16/group-tree/passive-refreshed.png b/applications/Volkov/artifacts/mame-p16/group-tree/passive-refreshed.png new file mode 100644 index 0000000..451d1d0 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p16/group-tree/passive-refreshed.png differ diff --git a/applications/Volkov/artifacts/mame-p16/group-tree/selected-roots.png b/applications/Volkov/artifacts/mame-p16/group-tree/selected-roots.png new file mode 100644 index 0000000..eec5d44 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p16/group-tree/selected-roots.png differ diff --git a/applications/Volkov/artifacts/mame-p17/tree-delete-cancel/clean-dss-return.png b/applications/Volkov/artifacts/mame-p17/tree-delete-cancel/clean-dss-return.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p17/tree-delete-cancel/clean-dss-return.png differ diff --git a/applications/Volkov/artifacts/mame-p17/tree-delete-cancel/group-confirm.png b/applications/Volkov/artifacts/mame-p17/tree-delete-cancel/group-confirm.png new file mode 100644 index 0000000..608e4ac Binary files /dev/null and b/applications/Volkov/artifacts/mame-p17/tree-delete-cancel/group-confirm.png differ diff --git a/applications/Volkov/artifacts/mame-p17/tree-delete-cancel/runtime-cancelled.png b/applications/Volkov/artifacts/mame-p17/tree-delete-cancel/runtime-cancelled.png new file mode 100644 index 0000000..1991252 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p17/tree-delete-cancel/runtime-cancelled.png differ diff --git a/applications/Volkov/artifacts/mame-p17/tree-delete-cancel/selected-before-cancel.png b/applications/Volkov/artifacts/mame-p17/tree-delete-cancel/selected-before-cancel.png new file mode 100644 index 0000000..c74ea62 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p17/tree-delete-cancel/selected-before-cancel.png differ diff --git a/applications/Volkov/artifacts/mame-p17/tree-delete/before.png b/applications/Volkov/artifacts/mame-p17/tree-delete/before.png new file mode 100644 index 0000000..33a943e Binary files /dev/null and b/applications/Volkov/artifacts/mame-p17/tree-delete/before.png differ diff --git a/applications/Volkov/artifacts/mame-p17/tree-delete/cancel-preserves.png b/applications/Volkov/artifacts/mame-p17/tree-delete/cancel-preserves.png new file mode 100644 index 0000000..20a457e Binary files /dev/null and b/applications/Volkov/artifacts/mame-p17/tree-delete/cancel-preserves.png differ diff --git a/applications/Volkov/artifacts/mame-p17/tree-delete/clean-dss-return.png b/applications/Volkov/artifacts/mame-p17/tree-delete/clean-dss-return.png new file mode 100644 index 0000000..a56f301 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p17/tree-delete/clean-dss-return.png differ diff --git a/applications/Volkov/artifacts/mame-p17/tree-delete/deleted-keep-preserved.png b/applications/Volkov/artifacts/mame-p17/tree-delete/deleted-keep-preserved.png new file mode 100644 index 0000000..31f72e7 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p17/tree-delete/deleted-keep-preserved.png differ diff --git a/applications/Volkov/artifacts/mame-p17/tree-delete/group-confirm.png b/applications/Volkov/artifacts/mame-p17/tree-delete/group-confirm.png new file mode 100644 index 0000000..f852fc9 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p17/tree-delete/group-confirm.png differ diff --git a/applications/Volkov/artifacts/mame-p17/tree-delete/selected-file-and-trees.png b/applications/Volkov/artifacts/mame-p17/tree-delete/selected-file-and-trees.png new file mode 100644 index 0000000..f39b794 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p17/tree-delete/selected-file-and-trees.png differ diff --git a/applications/Volkov/artifacts/mame-p18/metadata/clean-dss-return.png b/applications/Volkov/artifacts/mame-p18/metadata/clean-dss-return.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p18/metadata/clean-dss-return.png differ diff --git a/applications/Volkov/artifacts/mame-p18/metadata/copied-metadata.png b/applications/Volkov/artifacts/mame-p18/metadata/copied-metadata.png new file mode 100644 index 0000000..f4349d2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p18/metadata/copied-metadata.png differ diff --git a/applications/Volkov/artifacts/mame-p18/metadata/passive-metadata.png b/applications/Volkov/artifacts/mame-p18/metadata/passive-metadata.png new file mode 100644 index 0000000..fe2c52e Binary files /dev/null and b/applications/Volkov/artifacts/mame-p18/metadata/passive-metadata.png differ diff --git a/applications/Volkov/artifacts/mame-p18/metadata/source-metadata.png b/applications/Volkov/artifacts/mame-p18/metadata/source-metadata.png new file mode 100644 index 0000000..a3ca160 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p18/metadata/source-metadata.png differ diff --git a/applications/Volkov/artifacts/mame-p19/overwrite-core/all-pass.png b/applications/Volkov/artifacts/mame-p19/overwrite-core/all-pass.png new file mode 100644 index 0000000..c005e1a Binary files /dev/null and b/applications/Volkov/artifacts/mame-p19/overwrite-core/all-pass.png differ diff --git a/applications/Volkov/artifacts/mame-p19/overwrite-core/clean-dss-return.png b/applications/Volkov/artifacts/mame-p19/overwrite-core/clean-dss-return.png new file mode 100644 index 0000000..469db83 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p19/overwrite-core/clean-dss-return.png differ diff --git a/applications/Volkov/artifacts/mame-p19/policy/cancel-choice.png b/applications/Volkov/artifacts/mame-p19/policy/cancel-choice.png new file mode 100644 index 0000000..ec02cb1 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p19/policy/cancel-choice.png differ diff --git a/applications/Volkov/artifacts/mame-p19/policy/cancel-cleared.png b/applications/Volkov/artifacts/mame-p19/policy/cancel-cleared.png new file mode 100644 index 0000000..e4b0b7f Binary files /dev/null and b/applications/Volkov/artifacts/mame-p19/policy/cancel-cleared.png differ diff --git a/applications/Volkov/artifacts/mame-p19/policy/clean-dss-return.png b/applications/Volkov/artifacts/mame-p19/policy/clean-dss-return.png new file mode 100644 index 0000000..a56f301 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p19/policy/clean-dss-return.png differ diff --git a/applications/Volkov/artifacts/mame-p19/policy/overwrite-choice.png b/applications/Volkov/artifacts/mame-p19/policy/overwrite-choice.png new file mode 100644 index 0000000..a0d541a Binary files /dev/null and b/applications/Volkov/artifacts/mame-p19/policy/overwrite-choice.png differ diff --git a/applications/Volkov/artifacts/mame-p19/policy/overwrite-complete.png b/applications/Volkov/artifacts/mame-p19/policy/overwrite-complete.png new file mode 100644 index 0000000..d366f0b Binary files /dev/null and b/applications/Volkov/artifacts/mame-p19/policy/overwrite-complete.png differ diff --git a/applications/Volkov/artifacts/mame-p19/policy/readonly-reprompt.png b/applications/Volkov/artifacts/mame-p19/policy/readonly-reprompt.png new file mode 100644 index 0000000..0487799 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p19/policy/readonly-reprompt.png differ diff --git a/applications/Volkov/artifacts/mame-p19/policy/rename-choice.png b/applications/Volkov/artifacts/mame-p19/policy/rename-choice.png new file mode 100644 index 0000000..e18afd0 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p19/policy/rename-choice.png differ diff --git a/applications/Volkov/artifacts/mame-p19/policy/renamed.png b/applications/Volkov/artifacts/mame-p19/policy/renamed.png new file mode 100644 index 0000000..2307e3f Binary files /dev/null and b/applications/Volkov/artifacts/mame-p19/policy/renamed.png differ diff --git a/applications/Volkov/artifacts/mame-p19/policy/skip-all-choice.png b/applications/Volkov/artifacts/mame-p19/policy/skip-all-choice.png new file mode 100644 index 0000000..1477025 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p19/policy/skip-all-choice.png differ diff --git a/applications/Volkov/artifacts/mame-p19/policy/skip-complete.png b/applications/Volkov/artifacts/mame-p19/policy/skip-complete.png new file mode 100644 index 0000000..d55dbfe Binary files /dev/null and b/applications/Volkov/artifacts/mame-p19/policy/skip-complete.png differ diff --git a/applications/Volkov/artifacts/mame-p19/policy/target-result.png b/applications/Volkov/artifacts/mame-p19/policy/target-result.png new file mode 100644 index 0000000..81ac855 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p19/policy/target-result.png differ diff --git a/applications/Volkov/artifacts/mame-p2/README.md b/applications/Volkov/artifacts/mame-p2/README.md new file mode 100644 index 0000000..995b2d6 --- /dev/null +++ b/applications/Volkov/artifacts/mame-p2/README.md @@ -0,0 +1,56 @@ +# Артефакты MAME-прогонов P2 + +Дата: 6 сентября 2026 года. + +Окружение: MAME 0.287 (`b0c4527c`), машина `sprinter`, BIOS `v3.06`. +Программа `p2screen.exe` запускалась с дискеты A:. + +Каталог `screen/` содержит: + +- `full.png` — полный `WINREST` и сверка через `RDCHAR`; +- `partial-offset.png` — прямоугольник из EMM offset `0x2000`; +- `left-scroll-up.png` и `right-scroll-down.png` — оба направления scroll на + разных панелях; +- `stress-running.png` и `stress-final.png` — прогон 1000 операций и итоговая + сверка модели; +- `stable-phase-a.png`, `stable-phase-b.png`, `stable-phase-c.png` — три + идентичных кадра принятого варианта с системным атрибутом `0x30`; +- `flash-before-visible.png`, `flash-before-hidden.png` и + `flash-before-visible-again.png` — исторические диагностические имена. + Повторная RGB-проверка показала, что все три файла полностью идентичны; + прежний вывод о чередовании был ошибкой предпросмотра. + +Каталог `skeleton/` содержит именованные кадры первого запускаемого Commander: + +- `initial-left-active.png` и `tab-right-active.png` — старт и переключение + панели; +- `navigation-scroll.png` — PgDn+Down, cursor/top `28/2`; +- `quit-dialog.png`, `quit-cancelled-restored.png` — обе ветви первого + подтверждения; +- `reserved-command-dialog.png`, `message-closed-restored.png` — общий + message dialog и восстановление модели; +- `clean-exit-dos.png` — штатный выход; +- `repeat-second-launch.png`, `repeat-second-clean-exit.png` — второй процесс + после cleanup и его завершение. + +Это сохранённый одноатрибутный baseline. Каталог `skeleton-color/` повторяет +полный сценарий после подключения `sc_theme` и дополнительно содержит кадры +обоих красных quit-dialog. Актуальным результатом P2 считается +`skeleton-color/`. + +Каталог `palette/` содержит P2.2: + +- `phase-a.png`–`phase-e.png` — многоцветная сцена в разных моментах; +- `restored-dos.png` — возврат после восстановления сохранённых записей. + +В `phase-a.png` отличается только внешний индикатор Drive A в левой панели +layout MAME. Viewport Sprinter во всех пяти фазах пиксельно идентичен. Левая +служебная панель MAME в сравнение экранных тестов не входит. + +P2.1 не открывает BIOS-окна, не обращается к VRAM напрямую и не изменяет +палитру. Интерпретация и точные результаты приведены в +[`docs/p2-screen-probe-results.md`](../../docs/p2-screen-probe-results.md). +P2.2 проверяет разрешённые системные палитровые вызовы; результаты приведены +в [`docs/p2-palette-results.md`](../../docs/p2-palette-results.md). +Результаты приложения-skeleton приведены в +[`docs/p2-skeleton-results.md`](../../docs/p2-skeleton-results.md). diff --git a/applications/Volkov/artifacts/mame-p2/palette/phase-a.png b/applications/Volkov/artifacts/mame-p2/palette/phase-a.png new file mode 100644 index 0000000..3f35ed7 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/palette/phase-a.png differ diff --git a/applications/Volkov/artifacts/mame-p2/palette/phase-b.png b/applications/Volkov/artifacts/mame-p2/palette/phase-b.png new file mode 100644 index 0000000..d41755f Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/palette/phase-b.png differ diff --git a/applications/Volkov/artifacts/mame-p2/palette/phase-c.png b/applications/Volkov/artifacts/mame-p2/palette/phase-c.png new file mode 100644 index 0000000..d41755f Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/palette/phase-c.png differ diff --git a/applications/Volkov/artifacts/mame-p2/palette/phase-d.png b/applications/Volkov/artifacts/mame-p2/palette/phase-d.png new file mode 100644 index 0000000..d41755f Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/palette/phase-d.png differ diff --git a/applications/Volkov/artifacts/mame-p2/palette/phase-e.png b/applications/Volkov/artifacts/mame-p2/palette/phase-e.png new file mode 100644 index 0000000..d41755f Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/palette/phase-e.png differ diff --git a/applications/Volkov/artifacts/mame-p2/palette/restored-dos.png b/applications/Volkov/artifacts/mame-p2/palette/restored-dos.png new file mode 100644 index 0000000..815c5ef Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/palette/restored-dos.png differ diff --git a/applications/Volkov/artifacts/mame-p2/screen/flash-before-hidden.png b/applications/Volkov/artifacts/mame-p2/screen/flash-before-hidden.png new file mode 100644 index 0000000..2659006 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/screen/flash-before-hidden.png differ diff --git a/applications/Volkov/artifacts/mame-p2/screen/flash-before-visible-again.png b/applications/Volkov/artifacts/mame-p2/screen/flash-before-visible-again.png new file mode 100644 index 0000000..2659006 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/screen/flash-before-visible-again.png differ diff --git a/applications/Volkov/artifacts/mame-p2/screen/flash-before-visible.png b/applications/Volkov/artifacts/mame-p2/screen/flash-before-visible.png new file mode 100644 index 0000000..2659006 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/screen/flash-before-visible.png differ diff --git a/applications/Volkov/artifacts/mame-p2/screen/full.png b/applications/Volkov/artifacts/mame-p2/screen/full.png new file mode 100644 index 0000000..2e69c91 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/screen/full.png differ diff --git a/applications/Volkov/artifacts/mame-p2/screen/left-scroll-up.png b/applications/Volkov/artifacts/mame-p2/screen/left-scroll-up.png new file mode 100644 index 0000000..f2beaf0 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/screen/left-scroll-up.png differ diff --git a/applications/Volkov/artifacts/mame-p2/screen/partial-offset.png b/applications/Volkov/artifacts/mame-p2/screen/partial-offset.png new file mode 100644 index 0000000..bd3013c Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/screen/partial-offset.png differ diff --git a/applications/Volkov/artifacts/mame-p2/screen/right-scroll-down.png b/applications/Volkov/artifacts/mame-p2/screen/right-scroll-down.png new file mode 100644 index 0000000..fd2b965 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/screen/right-scroll-down.png differ diff --git a/applications/Volkov/artifacts/mame-p2/screen/stable-phase-a.png b/applications/Volkov/artifacts/mame-p2/screen/stable-phase-a.png new file mode 100644 index 0000000..8e88ec8 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/screen/stable-phase-a.png differ diff --git a/applications/Volkov/artifacts/mame-p2/screen/stable-phase-b.png b/applications/Volkov/artifacts/mame-p2/screen/stable-phase-b.png new file mode 100644 index 0000000..8e88ec8 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/screen/stable-phase-b.png differ diff --git a/applications/Volkov/artifacts/mame-p2/screen/stable-phase-c.png b/applications/Volkov/artifacts/mame-p2/screen/stable-phase-c.png new file mode 100644 index 0000000..8e88ec8 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/screen/stable-phase-c.png differ diff --git a/applications/Volkov/artifacts/mame-p2/screen/stress-final.png b/applications/Volkov/artifacts/mame-p2/screen/stress-final.png new file mode 100644 index 0000000..8e88ec8 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/screen/stress-final.png differ diff --git a/applications/Volkov/artifacts/mame-p2/screen/stress-running.png b/applications/Volkov/artifacts/mame-p2/screen/stress-running.png new file mode 100644 index 0000000..903b2c5 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/screen/stress-running.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton-color/clean-exit-dos.png b/applications/Volkov/artifacts/mame-p2/skeleton-color/clean-exit-dos.png new file mode 100644 index 0000000..815c5ef Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton-color/clean-exit-dos.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton-color/initial-left-active.png b/applications/Volkov/artifacts/mame-p2/skeleton-color/initial-left-active.png new file mode 100644 index 0000000..4471bd0 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton-color/initial-left-active.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton-color/message-closed-restored.png b/applications/Volkov/artifacts/mame-p2/skeleton-color/message-closed-restored.png new file mode 100644 index 0000000..3a4ba93 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton-color/message-closed-restored.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton-color/navigation-scroll.png b/applications/Volkov/artifacts/mame-p2/skeleton-color/navigation-scroll.png new file mode 100644 index 0000000..f5b8efb Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton-color/navigation-scroll.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton-color/quit-cancelled-restored.png b/applications/Volkov/artifacts/mame-p2/skeleton-color/quit-cancelled-restored.png new file mode 100644 index 0000000..d694559 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton-color/quit-cancelled-restored.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton-color/quit-dialog.png b/applications/Volkov/artifacts/mame-p2/skeleton-color/quit-dialog.png new file mode 100644 index 0000000..b7a412c Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton-color/quit-dialog.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton-color/repeat-second-clean-exit.png b/applications/Volkov/artifacts/mame-p2/skeleton-color/repeat-second-clean-exit.png new file mode 100644 index 0000000..815c5ef Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton-color/repeat-second-clean-exit.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton-color/repeat-second-launch.png b/applications/Volkov/artifacts/mame-p2/skeleton-color/repeat-second-launch.png new file mode 100644 index 0000000..4471bd0 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton-color/repeat-second-launch.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton-color/repeat-second-quit-dialog.png b/applications/Volkov/artifacts/mame-p2/skeleton-color/repeat-second-quit-dialog.png new file mode 100644 index 0000000..2ff9148 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton-color/repeat-second-quit-dialog.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton-color/reserved-command-dialog.png b/applications/Volkov/artifacts/mame-p2/skeleton-color/reserved-command-dialog.png new file mode 100644 index 0000000..cd5cdbb Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton-color/reserved-command-dialog.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton-color/second-quit-dialog.png b/applications/Volkov/artifacts/mame-p2/skeleton-color/second-quit-dialog.png new file mode 100644 index 0000000..05c1961 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton-color/second-quit-dialog.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton-color/tab-right-active.png b/applications/Volkov/artifacts/mame-p2/skeleton-color/tab-right-active.png new file mode 100644 index 0000000..b57a0b3 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton-color/tab-right-active.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton-repeat-run/sprinter/0000.png b/applications/Volkov/artifacts/mame-p2/skeleton-repeat-run/sprinter/0000.png new file mode 100644 index 0000000..3913e2c Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton-repeat-run/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton-repeat-run/sprinter/0001.png b/applications/Volkov/artifacts/mame-p2/skeleton-repeat-run/sprinter/0001.png new file mode 100644 index 0000000..f9297df Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton-repeat-run/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton-repeat-run/sprinter/0002.png b/applications/Volkov/artifacts/mame-p2/skeleton-repeat-run/sprinter/0002.png new file mode 100644 index 0000000..610bab7 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton-repeat-run/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton-repeat-run/sprinter/0003.png b/applications/Volkov/artifacts/mame-p2/skeleton-repeat-run/sprinter/0003.png new file mode 100644 index 0000000..c4a5543 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton-repeat-run/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton-repeat-run/sprinter/0004.png b/applications/Volkov/artifacts/mame-p2/skeleton-repeat-run/sprinter/0004.png new file mode 100644 index 0000000..d0705f2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton-repeat-run/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton-repeat-run/sprinter/0005.png b/applications/Volkov/artifacts/mame-p2/skeleton-repeat-run/sprinter/0005.png new file mode 100644 index 0000000..afc2ded Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton-repeat-run/sprinter/0005.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton-repeat-run/sprinter/0006.png b/applications/Volkov/artifacts/mame-p2/skeleton-repeat-run/sprinter/0006.png new file mode 100644 index 0000000..f6ff3cc Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton-repeat-run/sprinter/0006.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton-repeat-run/sprinter/0007.png b/applications/Volkov/artifacts/mame-p2/skeleton-repeat-run/sprinter/0007.png new file mode 100644 index 0000000..fc8d10f Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton-repeat-run/sprinter/0007.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton-repeat-run/sprinter/0008.png b/applications/Volkov/artifacts/mame-p2/skeleton-repeat-run/sprinter/0008.png new file mode 100644 index 0000000..815c5ef Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton-repeat-run/sprinter/0008.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton-repeat-run/sprinter/0009.png b/applications/Volkov/artifacts/mame-p2/skeleton-repeat-run/sprinter/0009.png new file mode 100644 index 0000000..3913e2c Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton-repeat-run/sprinter/0009.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton-repeat-run/sprinter/0010.png b/applications/Volkov/artifacts/mame-p2/skeleton-repeat-run/sprinter/0010.png new file mode 100644 index 0000000..66ca85d Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton-repeat-run/sprinter/0010.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton-repeat-run/sprinter/0011.png b/applications/Volkov/artifacts/mame-p2/skeleton-repeat-run/sprinter/0011.png new file mode 100644 index 0000000..815c5ef Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton-repeat-run/sprinter/0011.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton-ui-run/sprinter/0000.png b/applications/Volkov/artifacts/mame-p2/skeleton-ui-run/sprinter/0000.png new file mode 100644 index 0000000..3913e2c Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton-ui-run/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton-ui-run/sprinter/0001.png b/applications/Volkov/artifacts/mame-p2/skeleton-ui-run/sprinter/0001.png new file mode 100644 index 0000000..f9297df Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton-ui-run/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton-ui-run/sprinter/0002.png b/applications/Volkov/artifacts/mame-p2/skeleton-ui-run/sprinter/0002.png new file mode 100644 index 0000000..610bab7 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton-ui-run/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton-ui-run/sprinter/0003.png b/applications/Volkov/artifacts/mame-p2/skeleton-ui-run/sprinter/0003.png new file mode 100644 index 0000000..c4a5543 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton-ui-run/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton-ui-run/sprinter/0004.png b/applications/Volkov/artifacts/mame-p2/skeleton-ui-run/sprinter/0004.png new file mode 100644 index 0000000..d0705f2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton-ui-run/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton-ui-run/sprinter/0005.png b/applications/Volkov/artifacts/mame-p2/skeleton-ui-run/sprinter/0005.png new file mode 100644 index 0000000..afc2ded Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton-ui-run/sprinter/0005.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton-ui-run/sprinter/0006.png b/applications/Volkov/artifacts/mame-p2/skeleton-ui-run/sprinter/0006.png new file mode 100644 index 0000000..f6ff3cc Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton-ui-run/sprinter/0006.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton-ui-run/sprinter/0007.png b/applications/Volkov/artifacts/mame-p2/skeleton-ui-run/sprinter/0007.png new file mode 100644 index 0000000..fc8d10f Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton-ui-run/sprinter/0007.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton-ui-run/sprinter/0008.png b/applications/Volkov/artifacts/mame-p2/skeleton-ui-run/sprinter/0008.png new file mode 100644 index 0000000..815c5ef Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton-ui-run/sprinter/0008.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton/clean-exit-dos.png b/applications/Volkov/artifacts/mame-p2/skeleton/clean-exit-dos.png new file mode 100644 index 0000000..815c5ef Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton/clean-exit-dos.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton/cleanup-between-runs.png b/applications/Volkov/artifacts/mame-p2/skeleton/cleanup-between-runs.png new file mode 100644 index 0000000..815c5ef Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton/cleanup-between-runs.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton/initial-left-active.png b/applications/Volkov/artifacts/mame-p2/skeleton/initial-left-active.png new file mode 100644 index 0000000..3913e2c Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton/initial-left-active.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton/message-closed-restored.png b/applications/Volkov/artifacts/mame-p2/skeleton/message-closed-restored.png new file mode 100644 index 0000000..f6ff3cc Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton/message-closed-restored.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton/navigation-scroll.png b/applications/Volkov/artifacts/mame-p2/skeleton/navigation-scroll.png new file mode 100644 index 0000000..610bab7 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton/navigation-scroll.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton/quit-cancelled-restored.png b/applications/Volkov/artifacts/mame-p2/skeleton/quit-cancelled-restored.png new file mode 100644 index 0000000..d0705f2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton/quit-cancelled-restored.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton/quit-dialog.png b/applications/Volkov/artifacts/mame-p2/skeleton/quit-dialog.png new file mode 100644 index 0000000..c4a5543 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton/quit-dialog.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton/repeat-second-clean-exit.png b/applications/Volkov/artifacts/mame-p2/skeleton/repeat-second-clean-exit.png new file mode 100644 index 0000000..815c5ef Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton/repeat-second-clean-exit.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton/repeat-second-launch.png b/applications/Volkov/artifacts/mame-p2/skeleton/repeat-second-launch.png new file mode 100644 index 0000000..3913e2c Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton/repeat-second-launch.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton/reserved-command-dialog.png b/applications/Volkov/artifacts/mame-p2/skeleton/reserved-command-dialog.png new file mode 100644 index 0000000..afc2ded Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton/reserved-command-dialog.png differ diff --git a/applications/Volkov/artifacts/mame-p2/skeleton/tab-right-active.png b/applications/Volkov/artifacts/mame-p2/skeleton/tab-right-active.png new file mode 100644 index 0000000..f9297df Binary files /dev/null and b/applications/Volkov/artifacts/mame-p2/skeleton/tab-right-active.png differ diff --git a/applications/Volkov/artifacts/mame-p3/panels/bigdir-partial.png b/applications/Volkov/artifacts/mame-p3/panels/bigdir-partial.png new file mode 100644 index 0000000..23a6cf5 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p3/panels/bigdir-partial.png differ diff --git a/applications/Volkov/artifacts/mame-p3/panels/ctrl-pgup-left-only.png b/applications/Volkov/artifacts/mame-p3/panels/ctrl-pgup-left-only.png new file mode 100644 index 0000000..b66dea9 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p3/panels/ctrl-pgup-left-only.png differ diff --git a/applications/Volkov/artifacts/mame-p3/panels/dss-enum-limit.png b/applications/Volkov/artifacts/mame-p3/panels/dss-enum-limit.png new file mode 100644 index 0000000..3388fda Binary files /dev/null and b/applications/Volkov/artifacts/mame-p3/panels/dss-enum-limit.png differ diff --git a/applications/Volkov/artifacts/mame-p3/panels/dss-limit-clean-exit.png b/applications/Volkov/artifacts/mame-p3/panels/dss-limit-clean-exit.png new file mode 100644 index 0000000..815c5ef Binary files /dev/null and b/applications/Volkov/artifacts/mame-p3/panels/dss-limit-clean-exit.png differ diff --git a/applications/Volkov/artifacts/mame-p3/panels/dss-limit-quit-dialog.png b/applications/Volkov/artifacts/mame-p3/panels/dss-limit-quit-dialog.png new file mode 100644 index 0000000..4300ec7 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p3/panels/dss-limit-quit-dialog.png differ diff --git a/applications/Volkov/artifacts/mame-p3/panels/dss-marker-message.png b/applications/Volkov/artifacts/mame-p3/panels/dss-marker-message.png new file mode 100644 index 0000000..b58e144 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p3/panels/dss-marker-message.png differ diff --git a/applications/Volkov/artifacts/mame-p3/panels/dss-marker-selected.png b/applications/Volkov/artifacts/mame-p3/panels/dss-marker-selected.png new file mode 100644 index 0000000..cfe2ec1 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p3/panels/dss-marker-selected.png differ diff --git a/applications/Volkov/artifacts/mame-p3/panels/empty-parent.png b/applications/Volkov/artifacts/mame-p3/panels/empty-parent.png new file mode 100644 index 0000000..4919a72 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p3/panels/empty-parent.png differ diff --git a/applications/Volkov/artifacts/mame-p3/panels/enter-parent-return.png b/applications/Volkov/artifacts/mame-p3/panels/enter-parent-return.png new file mode 100644 index 0000000..9d6c1a3 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p3/panels/enter-parent-return.png differ diff --git a/applications/Volkov/artifacts/mame-p3/panels/first-clean-exit.png b/applications/Volkov/artifacts/mame-p3/panels/first-clean-exit.png new file mode 100644 index 0000000..815c5ef Binary files /dev/null and b/applications/Volkov/artifacts/mame-p3/panels/first-clean-exit.png differ diff --git a/applications/Volkov/artifacts/mame-p3/panels/independent-panel-state.png b/applications/Volkov/artifacts/mame-p3/panels/independent-panel-state.png new file mode 100644 index 0000000..fb1fb02 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p3/panels/independent-panel-state.png differ diff --git a/applications/Volkov/artifacts/mame-p3/panels/left-testdir-parent.png b/applications/Volkov/artifacts/mame-p3/panels/left-testdir-parent.png new file mode 100644 index 0000000..88d4484 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p3/panels/left-testdir-parent.png differ diff --git a/applications/Volkov/artifacts/mame-p3/panels/navigation-scroll.png b/applications/Volkov/artifacts/mame-p3/panels/navigation-scroll.png new file mode 100644 index 0000000..97cd85b Binary files /dev/null and b/applications/Volkov/artifacts/mame-p3/panels/navigation-scroll.png differ diff --git a/applications/Volkov/artifacts/mame-p3/panels/quit-dialog.png b/applications/Volkov/artifacts/mame-p3/panels/quit-dialog.png new file mode 100644 index 0000000..1fa966e Binary files /dev/null and b/applications/Volkov/artifacts/mame-p3/panels/quit-dialog.png differ diff --git a/applications/Volkov/artifacts/mame-p3/panels/refresh-preserves-inner.png b/applications/Volkov/artifacts/mame-p3/panels/refresh-preserves-inner.png new file mode 100644 index 0000000..dc34b34 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p3/panels/refresh-preserves-inner.png differ diff --git a/applications/Volkov/artifacts/mame-p3/panels/root-both-panels.png b/applications/Volkov/artifacts/mame-p3/panels/root-both-panels.png new file mode 100644 index 0000000..295c995 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p3/panels/root-both-panels.png differ diff --git a/applications/Volkov/artifacts/mame-p3/panels/scan-marker.png b/applications/Volkov/artifacts/mame-p3/panels/scan-marker.png new file mode 100644 index 0000000..d34838d Binary files /dev/null and b/applications/Volkov/artifacts/mame-p3/panels/scan-marker.png differ diff --git a/applications/Volkov/artifacts/mame-p3/panels/second-clean-exit.png b/applications/Volkov/artifacts/mame-p3/panels/second-clean-exit.png new file mode 100644 index 0000000..815c5ef Binary files /dev/null and b/applications/Volkov/artifacts/mame-p3/panels/second-clean-exit.png differ diff --git a/applications/Volkov/artifacts/mame-p3/panels/second-launch.png b/applications/Volkov/artifacts/mame-p3/panels/second-launch.png new file mode 100644 index 0000000..295c995 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p3/panels/second-launch.png differ diff --git a/applications/Volkov/artifacts/mame-p3/panels/second-quit-dialog.png b/applications/Volkov/artifacts/mame-p3/panels/second-quit-dialog.png new file mode 100644 index 0000000..28da7c4 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p3/panels/second-quit-dialog.png differ diff --git a/applications/Volkov/artifacts/mame-p3/panels/store-boundary-640-641.png b/applications/Volkov/artifacts/mame-p3/panels/store-boundary-640-641.png new file mode 100644 index 0000000..b2d47e2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p3/panels/store-boundary-640-641.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-hdd-diag/sprinter/0000.png b/applications/Volkov/artifacts/mame-p4/copy-hdd-diag/sprinter/0000.png new file mode 100644 index 0000000..ae73fce Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-hdd-diag/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-hdd-diag/sprinter/0001.png b/applications/Volkov/artifacts/mame-p4/copy-hdd-diag/sprinter/0001.png new file mode 100644 index 0000000..2a0af2f Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-hdd-diag/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-hdd-diag/sprinter/0002.png b/applications/Volkov/artifacts/mame-p4/copy-hdd-diag/sprinter/0002.png new file mode 100644 index 0000000..6663f1e Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-hdd-diag/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-hdd-diag/sprinter/0003.png b/applications/Volkov/artifacts/mame-p4/copy-hdd-diag/sprinter/0003.png new file mode 100644 index 0000000..c0030c7 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-hdd-diag/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-hdd-diag/sprinter/0004.png b/applications/Volkov/artifacts/mame-p4/copy-hdd-diag/sprinter/0004.png new file mode 100644 index 0000000..00442ab Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-hdd-diag/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-hdd-diag/sprinter/0005.png b/applications/Volkov/artifacts/mame-p4/copy-hdd-diag/sprinter/0005.png new file mode 100644 index 0000000..c1f7d9c Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-hdd-diag/sprinter/0005.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-hdd-diag/sprinter/0006.png b/applications/Volkov/artifacts/mame-p4/copy-hdd-diag/sprinter/0006.png new file mode 100644 index 0000000..bb29355 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-hdd-diag/sprinter/0006.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-hdd-diag/sprinter/0007.png b/applications/Volkov/artifacts/mame-p4/copy-hdd-diag/sprinter/0007.png new file mode 100644 index 0000000..b17b305 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-hdd-diag/sprinter/0007.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-hdd-diag/sprinter/0008.png b/applications/Volkov/artifacts/mame-p4/copy-hdd-diag/sprinter/0008.png new file mode 100644 index 0000000..99dafb9 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-hdd-diag/sprinter/0008.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-hdd-diag/sprinter/0009.png b/applications/Volkov/artifacts/mame-p4/copy-hdd-diag/sprinter/0009.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-hdd-diag/sprinter/0009.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-hdd-final/sprinter/0000.png b/applications/Volkov/artifacts/mame-p4/copy-hdd-final/sprinter/0000.png new file mode 100644 index 0000000..2004e42 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-hdd-final/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-hdd-final/sprinter/0001.png b/applications/Volkov/artifacts/mame-p4/copy-hdd-final/sprinter/0001.png new file mode 100644 index 0000000..2a0af2f Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-hdd-final/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-hdd-final/sprinter/0002.png b/applications/Volkov/artifacts/mame-p4/copy-hdd-final/sprinter/0002.png new file mode 100644 index 0000000..596ad61 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-hdd-final/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-hdd-final/sprinter/0003.png b/applications/Volkov/artifacts/mame-p4/copy-hdd-final/sprinter/0003.png new file mode 100644 index 0000000..0dfce4b Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-hdd-final/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-hdd-final/sprinter/0004.png b/applications/Volkov/artifacts/mame-p4/copy-hdd-final/sprinter/0004.png new file mode 100644 index 0000000..ee4a8cd Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-hdd-final/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-hdd-final/sprinter/0005.png b/applications/Volkov/artifacts/mame-p4/copy-hdd-final/sprinter/0005.png new file mode 100644 index 0000000..43c396a Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-hdd-final/sprinter/0005.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-hdd-final/sprinter/0006.png b/applications/Volkov/artifacts/mame-p4/copy-hdd-final/sprinter/0006.png new file mode 100644 index 0000000..c397f85 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-hdd-final/sprinter/0006.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-hdd-final/sprinter/0007.png b/applications/Volkov/artifacts/mame-p4/copy-hdd-final/sprinter/0007.png new file mode 100644 index 0000000..1e96936 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-hdd-final/sprinter/0007.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-hdd-final/sprinter/0008.png b/applications/Volkov/artifacts/mame-p4/copy-hdd-final/sprinter/0008.png new file mode 100644 index 0000000..b7b89a1 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-hdd-final/sprinter/0008.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-hdd-final/sprinter/0009.png b/applications/Volkov/artifacts/mame-p4/copy-hdd-final/sprinter/0009.png new file mode 100644 index 0000000..c090a6b Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-hdd-final/sprinter/0009.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-hdd-final/sprinter/0010.png b/applications/Volkov/artifacts/mame-p4/copy-hdd-final/sprinter/0010.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-hdd-final/sprinter/0010.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-hdd-fixed/sprinter/0000.png b/applications/Volkov/artifacts/mame-p4/copy-hdd-fixed/sprinter/0000.png new file mode 100644 index 0000000..2622789 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-hdd-fixed/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-hdd-fixed/sprinter/0001.png b/applications/Volkov/artifacts/mame-p4/copy-hdd-fixed/sprinter/0001.png new file mode 100644 index 0000000..2a0af2f Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-hdd-fixed/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-hdd-fixed/sprinter/0002.png b/applications/Volkov/artifacts/mame-p4/copy-hdd-fixed/sprinter/0002.png new file mode 100644 index 0000000..aadd8c4 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-hdd-fixed/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-hdd-fixed/sprinter/0003.png b/applications/Volkov/artifacts/mame-p4/copy-hdd-fixed/sprinter/0003.png new file mode 100644 index 0000000..155e16a Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-hdd-fixed/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-hdd-fixed/sprinter/0004.png b/applications/Volkov/artifacts/mame-p4/copy-hdd-fixed/sprinter/0004.png new file mode 100644 index 0000000..84a8c32 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-hdd-fixed/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-hdd-fixed/sprinter/0005.png b/applications/Volkov/artifacts/mame-p4/copy-hdd-fixed/sprinter/0005.png new file mode 100644 index 0000000..5c38baa Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-hdd-fixed/sprinter/0005.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-hdd-fixed/sprinter/0006.png b/applications/Volkov/artifacts/mame-p4/copy-hdd-fixed/sprinter/0006.png new file mode 100644 index 0000000..460081f Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-hdd-fixed/sprinter/0006.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-hdd-fixed/sprinter/0007.png b/applications/Volkov/artifacts/mame-p4/copy-hdd-fixed/sprinter/0007.png new file mode 100644 index 0000000..4cffd00 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-hdd-fixed/sprinter/0007.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-hdd-fixed/sprinter/0008.png b/applications/Volkov/artifacts/mame-p4/copy-hdd-fixed/sprinter/0008.png new file mode 100644 index 0000000..fe8716e Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-hdd-fixed/sprinter/0008.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-hdd-fixed/sprinter/0009.png b/applications/Volkov/artifacts/mame-p4/copy-hdd-fixed/sprinter/0009.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-hdd-fixed/sprinter/0009.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-hdd-run/sprinter/0000.png b/applications/Volkov/artifacts/mame-p4/copy-hdd-run/sprinter/0000.png new file mode 100644 index 0000000..c7a73e2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-hdd-run/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-hdd-run/sprinter/0001.png b/applications/Volkov/artifacts/mame-p4/copy-hdd-run/sprinter/0001.png new file mode 100644 index 0000000..2a0af2f Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-hdd-run/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-hdd-run/sprinter/0002.png b/applications/Volkov/artifacts/mame-p4/copy-hdd-run/sprinter/0002.png new file mode 100644 index 0000000..5fd7c2b Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-hdd-run/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-hdd-run/sprinter/0003.png b/applications/Volkov/artifacts/mame-p4/copy-hdd-run/sprinter/0003.png new file mode 100644 index 0000000..399137c Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-hdd-run/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-hdd-run/sprinter/0004.png b/applications/Volkov/artifacts/mame-p4/copy-hdd-run/sprinter/0004.png new file mode 100644 index 0000000..68e5625 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-hdd-run/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-hdd-run/sprinter/0005.png b/applications/Volkov/artifacts/mame-p4/copy-hdd-run/sprinter/0005.png new file mode 100644 index 0000000..5797306 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-hdd-run/sprinter/0005.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-hdd-run/sprinter/0006.png b/applications/Volkov/artifacts/mame-p4/copy-hdd-run/sprinter/0006.png new file mode 100644 index 0000000..c815ef3 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-hdd-run/sprinter/0006.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-hdd-run/sprinter/0007.png b/applications/Volkov/artifacts/mame-p4/copy-hdd-run/sprinter/0007.png new file mode 100644 index 0000000..bf947c3 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-hdd-run/sprinter/0007.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-hdd-run/sprinter/0008.png b/applications/Volkov/artifacts/mame-p4/copy-hdd-run/sprinter/0008.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-hdd-run/sprinter/0008.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-openfix/sprinter/0000.png b/applications/Volkov/artifacts/mame-p4/copy-openfix/sprinter/0000.png new file mode 100644 index 0000000..b5bd5b9 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-openfix/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-openfix/sprinter/0001.png b/applications/Volkov/artifacts/mame-p4/copy-openfix/sprinter/0001.png new file mode 100644 index 0000000..2a0af2f Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-openfix/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-openfix/sprinter/0002.png b/applications/Volkov/artifacts/mame-p4/copy-openfix/sprinter/0002.png new file mode 100644 index 0000000..11f2c63 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-openfix/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-openfix/sprinter/0003.png b/applications/Volkov/artifacts/mame-p4/copy-openfix/sprinter/0003.png new file mode 100644 index 0000000..a552fd3 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-openfix/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-openfix/sprinter/0004.png b/applications/Volkov/artifacts/mame-p4/copy-openfix/sprinter/0004.png new file mode 100644 index 0000000..55e321a Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-openfix/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-openfix/sprinter/0005.png b/applications/Volkov/artifacts/mame-p4/copy-openfix/sprinter/0005.png new file mode 100644 index 0000000..de4c53f Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-openfix/sprinter/0005.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-openfix/sprinter/0006.png b/applications/Volkov/artifacts/mame-p4/copy-openfix/sprinter/0006.png new file mode 100644 index 0000000..33c8501 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-openfix/sprinter/0006.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-openfix/sprinter/0007.png b/applications/Volkov/artifacts/mame-p4/copy-openfix/sprinter/0007.png new file mode 100644 index 0000000..7130408 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-openfix/sprinter/0007.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-openfix/sprinter/0008.png b/applications/Volkov/artifacts/mame-p4/copy-openfix/sprinter/0008.png new file mode 100644 index 0000000..a5e4d75 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-openfix/sprinter/0008.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-openfix/sprinter/0009.png b/applications/Volkov/artifacts/mame-p4/copy-openfix/sprinter/0009.png new file mode 100644 index 0000000..c180e33 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-openfix/sprinter/0009.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-openfix/sprinter/0010.png b/applications/Volkov/artifacts/mame-p4/copy-openfix/sprinter/0010.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-openfix/sprinter/0010.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-precommit-regression/sprinter/0000.png b/applications/Volkov/artifacts/mame-p4/copy-precommit-regression/sprinter/0000.png new file mode 100644 index 0000000..6cd5004 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-precommit-regression/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-precommit-regression/sprinter/0001.png b/applications/Volkov/artifacts/mame-p4/copy-precommit-regression/sprinter/0001.png new file mode 100644 index 0000000..2a0af2f Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-precommit-regression/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-precommit-regression/sprinter/0002.png b/applications/Volkov/artifacts/mame-p4/copy-precommit-regression/sprinter/0002.png new file mode 100644 index 0000000..2ee778a Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-precommit-regression/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-precommit-regression/sprinter/0003.png b/applications/Volkov/artifacts/mame-p4/copy-precommit-regression/sprinter/0003.png new file mode 100644 index 0000000..518d562 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-precommit-regression/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-precommit-regression/sprinter/0004.png b/applications/Volkov/artifacts/mame-p4/copy-precommit-regression/sprinter/0004.png new file mode 100644 index 0000000..e50d1d9 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-precommit-regression/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-precommit-regression/sprinter/0005.png b/applications/Volkov/artifacts/mame-p4/copy-precommit-regression/sprinter/0005.png new file mode 100644 index 0000000..4905279 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-precommit-regression/sprinter/0005.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-precommit-regression/sprinter/0006.png b/applications/Volkov/artifacts/mame-p4/copy-precommit-regression/sprinter/0006.png new file mode 100644 index 0000000..6086083 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-precommit-regression/sprinter/0006.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-precommit-regression/sprinter/0007.png b/applications/Volkov/artifacts/mame-p4/copy-precommit-regression/sprinter/0007.png new file mode 100644 index 0000000..f037bb8 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-precommit-regression/sprinter/0007.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-precommit-regression/sprinter/0008.png b/applications/Volkov/artifacts/mame-p4/copy-precommit-regression/sprinter/0008.png new file mode 100644 index 0000000..c63b73e Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-precommit-regression/sprinter/0008.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-precommit-regression/sprinter/0009.png b/applications/Volkov/artifacts/mame-p4/copy-precommit-regression/sprinter/0009.png new file mode 100644 index 0000000..fe75c00 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-precommit-regression/sprinter/0009.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy-precommit-regression/sprinter/0010.png b/applications/Volkov/artifacts/mame-p4/copy-precommit-regression/sprinter/0010.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy-precommit-regression/sprinter/0010.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy/bank-page-io.png b/applications/Volkov/artifacts/mame-p4/copy/bank-page-io.png new file mode 100644 index 0000000..6e0f2b0 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy/bank-page-io.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy/hdd-cancel-cleanup.png b/applications/Volkov/artifacts/mame-p4/copy/hdd-cancel-cleanup.png new file mode 100644 index 0000000..1e96936 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy/hdd-cancel-cleanup.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy/hdd-cancel-progress.png b/applications/Volkov/artifacts/mame-p4/copy/hdd-cancel-progress.png new file mode 100644 index 0000000..c397f85 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy/hdd-cancel-progress.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy/hdd-clean-exit.png b/applications/Volkov/artifacts/mame-p4/copy/hdd-clean-exit.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy/hdd-clean-exit.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy/hdd-copy-success.png b/applications/Volkov/artifacts/mame-p4/copy/hdd-copy-success.png new file mode 100644 index 0000000..ee4a8cd Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy/hdd-copy-success.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy/hdd-existing-refused.png b/applications/Volkov/artifacts/mame-p4/copy/hdd-existing-refused.png new file mode 100644 index 0000000..43c396a Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy/hdd-existing-refused.png differ diff --git a/applications/Volkov/artifacts/mame-p4/copy/hdd-zero-copy-success.png b/applications/Volkov/artifacts/mame-p4/copy/hdd-zero-copy-success.png new file mode 100644 index 0000000..b7b89a1 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/copy/hdd-zero-copy-success.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec-hdd-83-pass/sprinter/0000.png b/applications/Volkov/artifacts/mame-p4/exec-hdd-83-pass/sprinter/0000.png new file mode 100644 index 0000000..79c189f Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec-hdd-83-pass/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec-hdd-83-pass/sprinter/0001.png b/applications/Volkov/artifacts/mame-p4/exec-hdd-83-pass/sprinter/0001.png new file mode 100644 index 0000000..048351a Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec-hdd-83-pass/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec-hdd-83-pass/sprinter/0002.png b/applications/Volkov/artifacts/mame-p4/exec-hdd-83-pass/sprinter/0002.png new file mode 100644 index 0000000..1a8538c Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec-hdd-83-pass/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec-hdd-83-pass/sprinter/0003.png b/applications/Volkov/artifacts/mame-p4/exec-hdd-83-pass/sprinter/0003.png new file mode 100644 index 0000000..ee59798 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec-hdd-83-pass/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec-hdd-83-pass/sprinter/0004.png b/applications/Volkov/artifacts/mame-p4/exec-hdd-83-pass/sprinter/0004.png new file mode 100644 index 0000000..fbd3fd2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec-hdd-83-pass/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec-hdd-83-pass/sprinter/0005.png b/applications/Volkov/artifacts/mame-p4/exec-hdd-83-pass/sprinter/0005.png new file mode 100644 index 0000000..ffade7a Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec-hdd-83-pass/sprinter/0005.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec-hdd-83-pass/sprinter/0006.png b/applications/Volkov/artifacts/mame-p4/exec-hdd-83-pass/sprinter/0006.png new file mode 100644 index 0000000..27bc305 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec-hdd-83-pass/sprinter/0006.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec-hdd-83-pass/sprinter/0007.png b/applications/Volkov/artifacts/mame-p4/exec-hdd-83-pass/sprinter/0007.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec-hdd-83-pass/sprinter/0007.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec-hdd-final/sprinter/0000.png b/applications/Volkov/artifacts/mame-p4/exec-hdd-final/sprinter/0000.png new file mode 100644 index 0000000..63aa2fa Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec-hdd-final/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec-hdd-final/sprinter/0001.png b/applications/Volkov/artifacts/mame-p4/exec-hdd-final/sprinter/0001.png new file mode 100644 index 0000000..495a2ab Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec-hdd-final/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec-hdd-final/sprinter/0002.png b/applications/Volkov/artifacts/mame-p4/exec-hdd-final/sprinter/0002.png new file mode 100644 index 0000000..516e964 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec-hdd-final/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec-hdd-final/sprinter/0003.png b/applications/Volkov/artifacts/mame-p4/exec-hdd-final/sprinter/0003.png new file mode 100644 index 0000000..7985f43 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec-hdd-final/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec-hdd-final/sprinter/0004.png b/applications/Volkov/artifacts/mame-p4/exec-hdd-final/sprinter/0004.png new file mode 100644 index 0000000..8f4fd78 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec-hdd-final/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec-hdd-final/sprinter/0005.png b/applications/Volkov/artifacts/mame-p4/exec-hdd-final/sprinter/0005.png new file mode 100644 index 0000000..f0df585 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec-hdd-final/sprinter/0005.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec-hdd-final/sprinter/0006.png b/applications/Volkov/artifacts/mame-p4/exec-hdd-final/sprinter/0006.png new file mode 100644 index 0000000..fc0b552 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec-hdd-final/sprinter/0006.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec-hdd-final/sprinter/0007.png b/applications/Volkov/artifacts/mame-p4/exec-hdd-final/sprinter/0007.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec-hdd-final/sprinter/0007.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec-hdd-pass/sprinter/0000.png b/applications/Volkov/artifacts/mame-p4/exec-hdd-pass/sprinter/0000.png new file mode 100644 index 0000000..c3f0023 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec-hdd-pass/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec-hdd-pass/sprinter/0001.png b/applications/Volkov/artifacts/mame-p4/exec-hdd-pass/sprinter/0001.png new file mode 100644 index 0000000..fb5618c Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec-hdd-pass/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec-hdd-pass/sprinter/0002.png b/applications/Volkov/artifacts/mame-p4/exec-hdd-pass/sprinter/0002.png new file mode 100644 index 0000000..516e964 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec-hdd-pass/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec-hdd-pass/sprinter/0003.png b/applications/Volkov/artifacts/mame-p4/exec-hdd-pass/sprinter/0003.png new file mode 100644 index 0000000..2603413 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec-hdd-pass/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec-hdd-pass/sprinter/0004.png b/applications/Volkov/artifacts/mame-p4/exec-hdd-pass/sprinter/0004.png new file mode 100644 index 0000000..6095af7 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec-hdd-pass/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec-hdd-pass/sprinter/0005.png b/applications/Volkov/artifacts/mame-p4/exec-hdd-pass/sprinter/0005.png new file mode 100644 index 0000000..07ff09d Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec-hdd-pass/sprinter/0005.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec-hdd-pass/sprinter/0006.png b/applications/Volkov/artifacts/mame-p4/exec-hdd-pass/sprinter/0006.png new file mode 100644 index 0000000..1ef7243 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec-hdd-pass/sprinter/0006.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec-hdd-pass/sprinter/0007.png b/applications/Volkov/artifacts/mame-p4/exec-hdd-pass/sprinter/0007.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec-hdd-pass/sprinter/0007.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec-hdd-run-20260908/sprinter/0000.png b/applications/Volkov/artifacts/mame-p4/exec-hdd-run-20260908/sprinter/0000.png new file mode 100644 index 0000000..e56ee50 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec-hdd-run-20260908/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec-hdd-run-20260908/sprinter/0001.png b/applications/Volkov/artifacts/mame-p4/exec-hdd-run-20260908/sprinter/0001.png new file mode 100644 index 0000000..c900213 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec-hdd-run-20260908/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec-hdd-run-20260908/sprinter/0002.png b/applications/Volkov/artifacts/mame-p4/exec-hdd-run-20260908/sprinter/0002.png new file mode 100644 index 0000000..516e964 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec-hdd-run-20260908/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec-hdd-run-20260908/sprinter/0003.png b/applications/Volkov/artifacts/mame-p4/exec-hdd-run-20260908/sprinter/0003.png new file mode 100644 index 0000000..181c3c3 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec-hdd-run-20260908/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec-hdd-run-20260908/sprinter/0004.png b/applications/Volkov/artifacts/mame-p4/exec-hdd-run-20260908/sprinter/0004.png new file mode 100644 index 0000000..658d774 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec-hdd-run-20260908/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec-hdd-run-20260908/sprinter/0005.png b/applications/Volkov/artifacts/mame-p4/exec-hdd-run-20260908/sprinter/0005.png new file mode 100644 index 0000000..43eac69 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec-hdd-run-20260908/sprinter/0005.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec-hdd-run-20260908/sprinter/0006.png b/applications/Volkov/artifacts/mame-p4/exec-hdd-run-20260908/sprinter/0006.png new file mode 100644 index 0000000..0d7edd9 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec-hdd-run-20260908/sprinter/0006.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec-hdd-run-20260908/sprinter/0007.png b/applications/Volkov/artifacts/mame-p4/exec-hdd-run-20260908/sprinter/0007.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec-hdd-run-20260908/sprinter/0007.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec-regression-cwd/sprinter/0000.png b/applications/Volkov/artifacts/mame-p4/exec-regression-cwd/sprinter/0000.png new file mode 100644 index 0000000..0978008 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec-regression-cwd/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec-regression-cwd/sprinter/0001.png b/applications/Volkov/artifacts/mame-p4/exec-regression-cwd/sprinter/0001.png new file mode 100644 index 0000000..048351a Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec-regression-cwd/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec-regression-cwd/sprinter/0002.png b/applications/Volkov/artifacts/mame-p4/exec-regression-cwd/sprinter/0002.png new file mode 100644 index 0000000..1a8538c Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec-regression-cwd/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec-regression-cwd/sprinter/0003.png b/applications/Volkov/artifacts/mame-p4/exec-regression-cwd/sprinter/0003.png new file mode 100644 index 0000000..09bb86f Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec-regression-cwd/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec-regression-cwd/sprinter/0004.png b/applications/Volkov/artifacts/mame-p4/exec-regression-cwd/sprinter/0004.png new file mode 100644 index 0000000..6fed65f Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec-regression-cwd/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec-regression-cwd/sprinter/0005.png b/applications/Volkov/artifacts/mame-p4/exec-regression-cwd/sprinter/0005.png new file mode 100644 index 0000000..9ef735a Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec-regression-cwd/sprinter/0005.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec-regression-cwd/sprinter/0006.png b/applications/Volkov/artifacts/mame-p4/exec-regression-cwd/sprinter/0006.png new file mode 100644 index 0000000..522af92 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec-regression-cwd/sprinter/0006.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec-regression-cwd/sprinter/0007.png b/applications/Volkov/artifacts/mame-p4/exec-regression-cwd/sprinter/0007.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec-regression-cwd/sprinter/0007.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec/broken-exe-error.png b/applications/Volkov/artifacts/mame-p4/exec/broken-exe-error.png new file mode 100644 index 0000000..fbd3fd2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec/broken-exe-error.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec/child-mode-palette-cwd.png b/applications/Volkov/artifacts/mame-p4/exec/child-mode-palette-cwd.png new file mode 100644 index 0000000..1a8538c Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec/child-mode-palette-cwd.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec/clean-dss-return.png b/applications/Volkov/artifacts/mame-p4/exec/clean-dss-return.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec/clean-dss-return.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec/commander-restored-both-panels.png b/applications/Volkov/artifacts/mame-p4/exec/commander-restored-both-panels.png new file mode 100644 index 0000000..ee59798 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec/commander-restored-both-panels.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec/quit-after-exec.png b/applications/Volkov/artifacts/mame-p4/exec/quit-after-exec.png new file mode 100644 index 0000000..27bc305 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec/quit-after-exec.png differ diff --git a/applications/Volkov/artifacts/mame-p4/exec/unsupported-file.png b/applications/Volkov/artifacts/mame-p4/exec/unsupported-file.png new file mode 100644 index 0000000..ffade7a Binary files /dev/null and b/applications/Volkov/artifacts/mame-p4/exec/unsupported-file.png differ diff --git a/applications/Volkov/artifacts/mame-p5/copy-faults-final/sprinter/0000.png b/applications/Volkov/artifacts/mame-p5/copy-faults-final/sprinter/0000.png new file mode 100644 index 0000000..96ab6f6 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/copy-faults-final/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p5/copy-faults-final/sprinter/0001.png b/applications/Volkov/artifacts/mame-p5/copy-faults-final/sprinter/0001.png new file mode 100644 index 0000000..7b5bf9f Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/copy-faults-final/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p5/copy-faults-missing-exe/sprinter/0000.png b/applications/Volkov/artifacts/mame-p5/copy-faults-missing-exe/sprinter/0000.png new file mode 100644 index 0000000..3ce4a0e Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/copy-faults-missing-exe/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p5/copy-faults-missing-exe/sprinter/0001.png b/applications/Volkov/artifacts/mame-p5/copy-faults-missing-exe/sprinter/0001.png new file mode 100644 index 0000000..79378f8 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/copy-faults-missing-exe/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p5/copy-faults-pass/sprinter/0000.png b/applications/Volkov/artifacts/mame-p5/copy-faults-pass/sprinter/0000.png new file mode 100644 index 0000000..96ab6f6 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/copy-faults-pass/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p5/copy-faults-pass/sprinter/0001.png b/applications/Volkov/artifacts/mame-p5/copy-faults-pass/sprinter/0001.png new file mode 100644 index 0000000..7b5bf9f Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/copy-faults-pass/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p5/copy-faults-readonly-openfix/sprinter/0000.png b/applications/Volkov/artifacts/mame-p5/copy-faults-readonly-openfix/sprinter/0000.png new file mode 100644 index 0000000..958029b Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/copy-faults-readonly-openfix/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p5/copy-faults-readonly-openfix/sprinter/0001.png b/applications/Volkov/artifacts/mame-p5/copy-faults-readonly-openfix/sprinter/0001.png new file mode 100644 index 0000000..e2b2ceb Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/copy-faults-readonly-openfix/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p5/copy-faults-readonly/sprinter/0000.png b/applications/Volkov/artifacts/mame-p5/copy-faults-readonly/sprinter/0000.png new file mode 100644 index 0000000..205d997 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/copy-faults-readonly/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p5/copy-faults-readonly/sprinter/0001.png b/applications/Volkov/artifacts/mame-p5/copy-faults-readonly/sprinter/0001.png new file mode 100644 index 0000000..87c3aff Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/copy-faults-readonly/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p5/copy-faults-run/sprinter/0000.png b/applications/Volkov/artifacts/mame-p5/copy-faults-run/sprinter/0000.png new file mode 100644 index 0000000..c9f2633 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/copy-faults-run/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p5/copy-faults-run/sprinter/0001.png b/applications/Volkov/artifacts/mame-p5/copy-faults-run/sprinter/0001.png new file mode 100644 index 0000000..15329bd Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/copy-faults-run/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p5/copy-faults-second/sprinter/0000.png b/applications/Volkov/artifacts/mame-p5/copy-faults-second/sprinter/0000.png new file mode 100644 index 0000000..7b5bf9f Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/copy-faults-second/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p5/copy-faults-second/sprinter/0001.png b/applications/Volkov/artifacts/mame-p5/copy-faults-second/sprinter/0001.png new file mode 100644 index 0000000..7b5bf9f Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/copy-faults-second/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p5/copy-faults-visible/sprinter/0000.png b/applications/Volkov/artifacts/mame-p5/copy-faults-visible/sprinter/0000.png new file mode 100644 index 0000000..32dcd7c Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/copy-faults-visible/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p5/copy-faults-visible/sprinter/0001.png b/applications/Volkov/artifacts/mame-p5/copy-faults-visible/sprinter/0001.png new file mode 100644 index 0000000..34ba3e2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/copy-faults-visible/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p5/copy-faults/fault-matrix-all-pass.png b/applications/Volkov/artifacts/mame-p5/copy-faults/fault-matrix-all-pass.png new file mode 100644 index 0000000..3ce4a0e Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/copy-faults/fault-matrix-all-pass.png differ diff --git a/applications/Volkov/artifacts/mame-p5/copy-faults/return-to-dss.png b/applications/Volkov/artifacts/mame-p5/copy-faults/return-to-dss.png new file mode 100644 index 0000000..79378f8 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/copy-faults/return-to-dss.png differ diff --git a/applications/Volkov/artifacts/mame-p5/enospc-precommit/sprinter/0000.png b/applications/Volkov/artifacts/mame-p5/enospc-precommit/sprinter/0000.png new file mode 100644 index 0000000..494a63b Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/enospc-precommit/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p5/enospc-precommit/sprinter/0001.png b/applications/Volkov/artifacts/mame-p5/enospc-precommit/sprinter/0001.png new file mode 100644 index 0000000..69e0363 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/enospc-precommit/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p5/enospc-precommit/sprinter/0002.png b/applications/Volkov/artifacts/mame-p5/enospc-precommit/sprinter/0002.png new file mode 100644 index 0000000..d3204d8 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/enospc-precommit/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p5/enospc-precommit/sprinter/0003.png b/applications/Volkov/artifacts/mame-p5/enospc-precommit/sprinter/0003.png new file mode 100644 index 0000000..7d82a67 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/enospc-precommit/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p5/enospc-precommit/sprinter/0004.png b/applications/Volkov/artifacts/mame-p5/enospc-precommit/sprinter/0004.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/enospc-precommit/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p5/enospc-run/sprinter/0000.png b/applications/Volkov/artifacts/mame-p5/enospc-run/sprinter/0000.png new file mode 100644 index 0000000..494a63b Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/enospc-run/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p5/enospc-run/sprinter/0001.png b/applications/Volkov/artifacts/mame-p5/enospc-run/sprinter/0001.png new file mode 100644 index 0000000..2b27e3e Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/enospc-run/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p5/enospc-run/sprinter/0002.png b/applications/Volkov/artifacts/mame-p5/enospc-run/sprinter/0002.png new file mode 100644 index 0000000..e8a9344 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/enospc-run/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p5/enospc-run/sprinter/0003.png b/applications/Volkov/artifacts/mame-p5/enospc-run/sprinter/0003.png new file mode 100644 index 0000000..80e6e95 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/enospc-run/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p5/enospc-run/sprinter/0004.png b/applications/Volkov/artifacts/mame-p5/enospc-run/sprinter/0004.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/enospc-run/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p5/enospc/clean-dss-return.png b/applications/Volkov/artifacts/mame-p5/enospc/clean-dss-return.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/enospc/clean-dss-return.png differ diff --git a/applications/Volkov/artifacts/mame-p5/enospc/copy-progress.png b/applications/Volkov/artifacts/mame-p5/enospc/copy-progress.png new file mode 100644 index 0000000..2b27e3e Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/enospc/copy-progress.png differ diff --git a/applications/Volkov/artifacts/mame-p5/enospc/enospc-cleanup.png b/applications/Volkov/artifacts/mame-p5/enospc/enospc-cleanup.png new file mode 100644 index 0000000..e8a9344 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/enospc/enospc-cleanup.png differ diff --git a/applications/Volkov/artifacts/mame-p5/enospc/quit-after-error.png b/applications/Volkov/artifacts/mame-p5/enospc/quit-after-error.png new file mode 100644 index 0000000..80e6e95 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/enospc/quit-after-error.png differ diff --git a/applications/Volkov/artifacts/mame-p5/media-change-copy/sprinter/0000.png b/applications/Volkov/artifacts/mame-p5/media-change-copy/sprinter/0000.png new file mode 100644 index 0000000..f81507c Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/media-change-copy/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p5/media-change-copy/sprinter/0001.png b/applications/Volkov/artifacts/mame-p5/media-change-copy/sprinter/0001.png new file mode 100644 index 0000000..b87e161 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/media-change-copy/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p5/media-change-copy/sprinter/0002.png b/applications/Volkov/artifacts/mame-p5/media-change-copy/sprinter/0002.png new file mode 100644 index 0000000..b87e161 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/media-change-copy/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p5/media-change-copy/sprinter/0003.png b/applications/Volkov/artifacts/mame-p5/media-change-copy/sprinter/0003.png new file mode 100644 index 0000000..b0ceb44 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/media-change-copy/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p5/media-change-copy/sprinter/0004.png b/applications/Volkov/artifacts/mame-p5/media-change-copy/sprinter/0004.png new file mode 100644 index 0000000..6221f8f Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/media-change-copy/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p5/media-change-copy/sprinter/0005.png b/applications/Volkov/artifacts/mame-p5/media-change-copy/sprinter/0005.png new file mode 100644 index 0000000..ae58397 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/media-change-copy/sprinter/0005.png differ diff --git a/applications/Volkov/artifacts/mame-p5/media-change-copy/sprinter/0006.png b/applications/Volkov/artifacts/mame-p5/media-change-copy/sprinter/0006.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/media-change-copy/sprinter/0006.png differ diff --git a/applications/Volkov/artifacts/mame-p5/media-change-run/sprinter/0000.png b/applications/Volkov/artifacts/mame-p5/media-change-run/sprinter/0000.png new file mode 100644 index 0000000..c0855aa Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/media-change-run/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p5/media-change-run/sprinter/0001.png b/applications/Volkov/artifacts/mame-p5/media-change-run/sprinter/0001.png new file mode 100644 index 0000000..ad59aaa Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/media-change-run/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p5/media-change-run/sprinter/0002.png b/applications/Volkov/artifacts/mame-p5/media-change-run/sprinter/0002.png new file mode 100644 index 0000000..ad59aaa Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/media-change-run/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p5/media-change-run/sprinter/0003.png b/applications/Volkov/artifacts/mame-p5/media-change-run/sprinter/0003.png new file mode 100644 index 0000000..ad59aaa Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/media-change-run/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p5/media-change-run/sprinter/0004.png b/applications/Volkov/artifacts/mame-p5/media-change-run/sprinter/0004.png new file mode 100644 index 0000000..8aac37c Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/media-change-run/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p5/media-change-run/sprinter/0005.png b/applications/Volkov/artifacts/mame-p5/media-change-run/sprinter/0005.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/media-change-run/sprinter/0005.png differ diff --git a/applications/Volkov/artifacts/mame-p5/media-change/clean-dss-return.png b/applications/Volkov/artifacts/mame-p5/media-change/clean-dss-return.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/media-change/clean-dss-return.png differ diff --git a/applications/Volkov/artifacts/mame-p5/media-change/managed-error.png b/applications/Volkov/artifacts/mame-p5/media-change/managed-error.png new file mode 100644 index 0000000..b0ceb44 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/media-change/managed-error.png differ diff --git a/applications/Volkov/artifacts/mame-p5/media-change/recovered-copy.png b/applications/Volkov/artifacts/mame-p5/media-change/recovered-copy.png new file mode 100644 index 0000000..ae58397 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/media-change/recovered-copy.png differ diff --git a/applications/Volkov/artifacts/mame-p5/media-change/retry-progress.png b/applications/Volkov/artifacts/mame-p5/media-change/retry-progress.png new file mode 100644 index 0000000..6221f8f Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/media-change/retry-progress.png differ diff --git a/applications/Volkov/artifacts/mame-p5/panel-edges-run/sprinter/0000.png b/applications/Volkov/artifacts/mame-p5/panel-edges-run/sprinter/0000.png new file mode 100644 index 0000000..de70a3f Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/panel-edges-run/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p5/panel-edges-run/sprinter/0001.png b/applications/Volkov/artifacts/mame-p5/panel-edges-run/sprinter/0001.png new file mode 100644 index 0000000..4060496 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/panel-edges-run/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p5/panel-edges-run/sprinter/0002.png b/applications/Volkov/artifacts/mame-p5/panel-edges-run/sprinter/0002.png new file mode 100644 index 0000000..647eea1 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/panel-edges-run/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p5/panel-edges-run/sprinter/0003.png b/applications/Volkov/artifacts/mame-p5/panel-edges-run/sprinter/0003.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/panel-edges-run/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p5/panel-edges/clean-dss-return.png b/applications/Volkov/artifacts/mame-p5/panel-edges/clean-dss-return.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/panel-edges/clean-dss-return.png differ diff --git a/applications/Volkov/artifacts/mame-p5/panel-edges/count-27-fits.png b/applications/Volkov/artifacts/mame-p5/panel-edges/count-27-fits.png new file mode 100644 index 0000000..647eea1 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/panel-edges/count-27-fits.png differ diff --git a/applications/Volkov/artifacts/mame-p5/panel-edges/count-28-scrolls.png b/applications/Volkov/artifacts/mame-p5/panel-edges/count-28-scrolls.png new file mode 100644 index 0000000..4060496 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/panel-edges/count-28-scrolls.png differ diff --git a/applications/Volkov/artifacts/mame-p5/panel-edges/counts-27-28.png b/applications/Volkov/artifacts/mame-p5/panel-edges/counts-27-28.png new file mode 100644 index 0000000..de70a3f Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/panel-edges/counts-27-28.png differ diff --git a/applications/Volkov/artifacts/mame-p5/stability-pass/sprinter/0000.png b/applications/Volkov/artifacts/mame-p5/stability-pass/sprinter/0000.png new file mode 100644 index 0000000..7e36769 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/stability-pass/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p5/stability-pass/sprinter/0001.png b/applications/Volkov/artifacts/mame-p5/stability-pass/sprinter/0001.png new file mode 100644 index 0000000..11967ae Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/stability-pass/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p5/stability-pass/sprinter/0002.png b/applications/Volkov/artifacts/mame-p5/stability-pass/sprinter/0002.png new file mode 100644 index 0000000..89d9f91 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/stability-pass/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p5/stability-pass/sprinter/0003.png b/applications/Volkov/artifacts/mame-p5/stability-pass/sprinter/0003.png new file mode 100644 index 0000000..5075265 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/stability-pass/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p5/stability-pass/sprinter/0004.png b/applications/Volkov/artifacts/mame-p5/stability-pass/sprinter/0004.png new file mode 100644 index 0000000..28a4dc9 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/stability-pass/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p5/stability-pass/sprinter/0005.png b/applications/Volkov/artifacts/mame-p5/stability-pass/sprinter/0005.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/stability-pass/sprinter/0005.png differ diff --git a/applications/Volkov/artifacts/mame-p5/stability-pass/sprinter/0006.png b/applications/Volkov/artifacts/mame-p5/stability-pass/sprinter/0006.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/stability-pass/sprinter/0006.png differ diff --git a/applications/Volkov/artifacts/mame-p5/stability-pass/sprinter/0007.png b/applications/Volkov/artifacts/mame-p5/stability-pass/sprinter/0007.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/stability-pass/sprinter/0007.png differ diff --git a/applications/Volkov/artifacts/mame-p5/stability-precommit/sprinter/0000.png b/applications/Volkov/artifacts/mame-p5/stability-precommit/sprinter/0000.png new file mode 100644 index 0000000..81014d2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/stability-precommit/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p5/stability-precommit/sprinter/0001.png b/applications/Volkov/artifacts/mame-p5/stability-precommit/sprinter/0001.png new file mode 100644 index 0000000..11967ae Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/stability-precommit/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p5/stability-precommit/sprinter/0002.png b/applications/Volkov/artifacts/mame-p5/stability-precommit/sprinter/0002.png new file mode 100644 index 0000000..62a4b00 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/stability-precommit/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p5/stability-precommit/sprinter/0003.png b/applications/Volkov/artifacts/mame-p5/stability-precommit/sprinter/0003.png new file mode 100644 index 0000000..2e91bcf Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/stability-precommit/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p5/stability-precommit/sprinter/0004.png b/applications/Volkov/artifacts/mame-p5/stability-precommit/sprinter/0004.png new file mode 100644 index 0000000..b937fee Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/stability-precommit/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p5/stability-precommit/sprinter/0005.png b/applications/Volkov/artifacts/mame-p5/stability-precommit/sprinter/0005.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/stability-precommit/sprinter/0005.png differ diff --git a/applications/Volkov/artifacts/mame-p5/stability-precommit/sprinter/0006.png b/applications/Volkov/artifacts/mame-p5/stability-precommit/sprinter/0006.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/stability-precommit/sprinter/0006.png differ diff --git a/applications/Volkov/artifacts/mame-p5/stability-precommit/sprinter/0007.png b/applications/Volkov/artifacts/mame-p5/stability-precommit/sprinter/0007.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/stability-precommit/sprinter/0007.png differ diff --git a/applications/Volkov/artifacts/mame-p5/stability-run/sprinter/0000.png b/applications/Volkov/artifacts/mame-p5/stability-run/sprinter/0000.png new file mode 100644 index 0000000..79ba180 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/stability-run/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p5/stability-run/sprinter/0001.png b/applications/Volkov/artifacts/mame-p5/stability-run/sprinter/0001.png new file mode 100644 index 0000000..11967ae Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/stability-run/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p5/stability-run/sprinter/0002.png b/applications/Volkov/artifacts/mame-p5/stability-run/sprinter/0002.png new file mode 100644 index 0000000..11b5069 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/stability-run/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p5/stability-run/sprinter/0003.png b/applications/Volkov/artifacts/mame-p5/stability-run/sprinter/0003.png new file mode 100644 index 0000000..c6a7eb7 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/stability-run/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p5/stability-run/sprinter/0004.png b/applications/Volkov/artifacts/mame-p5/stability-run/sprinter/0004.png new file mode 100644 index 0000000..fe62857 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/stability-run/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p5/stability-run/sprinter/0005.png b/applications/Volkov/artifacts/mame-p5/stability-run/sprinter/0005.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/stability-run/sprinter/0005.png differ diff --git a/applications/Volkov/artifacts/mame-p5/stability-run/sprinter/0006.png b/applications/Volkov/artifacts/mame-p5/stability-run/sprinter/0006.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/stability-run/sprinter/0006.png differ diff --git a/applications/Volkov/artifacts/mame-p5/stability-run/sprinter/0007.png b/applications/Volkov/artifacts/mame-p5/stability-run/sprinter/0007.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/stability-run/sprinter/0007.png differ diff --git a/applications/Volkov/artifacts/mame-p5/stability/cleanup-cycle-1.png b/applications/Volkov/artifacts/mame-p5/stability/cleanup-cycle-1.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/stability/cleanup-cycle-1.png differ diff --git a/applications/Volkov/artifacts/mame-p5/stability/cleanup-cycle-2.png b/applications/Volkov/artifacts/mame-p5/stability/cleanup-cycle-2.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/stability/cleanup-cycle-2.png differ diff --git a/applications/Volkov/artifacts/mame-p5/stability/cleanup-cycle-3.png b/applications/Volkov/artifacts/mame-p5/stability/cleanup-cycle-3.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/stability/cleanup-cycle-3.png differ diff --git a/applications/Volkov/artifacts/mame-p5/stability/copy-large-progress.png b/applications/Volkov/artifacts/mame-p5/stability/copy-large-progress.png new file mode 100644 index 0000000..89d9f91 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/stability/copy-large-progress.png differ diff --git a/applications/Volkov/artifacts/mame-p5/stability/twenty-copy-hundred-refresh.png b/applications/Volkov/artifacts/mame-p5/stability/twenty-copy-hundred-refresh.png new file mode 100644 index 0000000..28a4dc9 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/stability/twenty-copy-hundred-refresh.png differ diff --git a/applications/Volkov/artifacts/mame-p5/viewer-openfix/sprinter/0000.png b/applications/Volkov/artifacts/mame-p5/viewer-openfix/sprinter/0000.png new file mode 100644 index 0000000..e20e03d Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/viewer-openfix/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p5/viewer-openfix/sprinter/0001.png b/applications/Volkov/artifacts/mame-p5/viewer-openfix/sprinter/0001.png new file mode 100644 index 0000000..e99e281 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/viewer-openfix/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p5/viewer-openfix/sprinter/0002.png b/applications/Volkov/artifacts/mame-p5/viewer-openfix/sprinter/0002.png new file mode 100644 index 0000000..b13058f Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/viewer-openfix/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p5/viewer-openfix/sprinter/0003.png b/applications/Volkov/artifacts/mame-p5/viewer-openfix/sprinter/0003.png new file mode 100644 index 0000000..2140158 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/viewer-openfix/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p5/viewer-openfix/sprinter/0004.png b/applications/Volkov/artifacts/mame-p5/viewer-openfix/sprinter/0004.png new file mode 100644 index 0000000..f6d1b58 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/viewer-openfix/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p5/viewer-openfix/sprinter/0005.png b/applications/Volkov/artifacts/mame-p5/viewer-openfix/sprinter/0005.png new file mode 100644 index 0000000..c6f1929 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/viewer-openfix/sprinter/0005.png differ diff --git a/applications/Volkov/artifacts/mame-p5/viewer-openfix/sprinter/0006.png b/applications/Volkov/artifacts/mame-p5/viewer-openfix/sprinter/0006.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/viewer-openfix/sprinter/0006.png differ diff --git a/applications/Volkov/artifacts/mame-p5/viewer-pass/sprinter/0000.png b/applications/Volkov/artifacts/mame-p5/viewer-pass/sprinter/0000.png new file mode 100644 index 0000000..e1c3be5 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/viewer-pass/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p5/viewer-pass/sprinter/0001.png b/applications/Volkov/artifacts/mame-p5/viewer-pass/sprinter/0001.png new file mode 100644 index 0000000..e99e281 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/viewer-pass/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p5/viewer-pass/sprinter/0002.png b/applications/Volkov/artifacts/mame-p5/viewer-pass/sprinter/0002.png new file mode 100644 index 0000000..b13058f Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/viewer-pass/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p5/viewer-pass/sprinter/0003.png b/applications/Volkov/artifacts/mame-p5/viewer-pass/sprinter/0003.png new file mode 100644 index 0000000..2140158 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/viewer-pass/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p5/viewer-pass/sprinter/0004.png b/applications/Volkov/artifacts/mame-p5/viewer-pass/sprinter/0004.png new file mode 100644 index 0000000..58cda43 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/viewer-pass/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p5/viewer-pass/sprinter/0005.png b/applications/Volkov/artifacts/mame-p5/viewer-pass/sprinter/0005.png new file mode 100644 index 0000000..54d5a2e Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/viewer-pass/sprinter/0005.png differ diff --git a/applications/Volkov/artifacts/mame-p5/viewer-pass/sprinter/0006.png b/applications/Volkov/artifacts/mame-p5/viewer-pass/sprinter/0006.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/viewer-pass/sprinter/0006.png differ diff --git a/applications/Volkov/artifacts/mame-p5/viewer-run/sprinter/0000.png b/applications/Volkov/artifacts/mame-p5/viewer-run/sprinter/0000.png new file mode 100644 index 0000000..e803714 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/viewer-run/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p5/viewer-run/sprinter/0001.png b/applications/Volkov/artifacts/mame-p5/viewer-run/sprinter/0001.png new file mode 100644 index 0000000..e99e281 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/viewer-run/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p5/viewer-run/sprinter/0002.png b/applications/Volkov/artifacts/mame-p5/viewer-run/sprinter/0002.png new file mode 100644 index 0000000..b13058f Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/viewer-run/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p5/viewer-run/sprinter/0003.png b/applications/Volkov/artifacts/mame-p5/viewer-run/sprinter/0003.png new file mode 100644 index 0000000..2140158 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/viewer-run/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p5/viewer-run/sprinter/0004.png b/applications/Volkov/artifacts/mame-p5/viewer-run/sprinter/0004.png new file mode 100644 index 0000000..0e7c4bb Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/viewer-run/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p5/viewer-run/sprinter/0005.png b/applications/Volkov/artifacts/mame-p5/viewer-run/sprinter/0005.png new file mode 100644 index 0000000..ff7e047 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/viewer-run/sprinter/0005.png differ diff --git a/applications/Volkov/artifacts/mame-p5/viewer-run/sprinter/0006.png b/applications/Volkov/artifacts/mame-p5/viewer-run/sprinter/0006.png new file mode 100644 index 0000000..0b317b3 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/viewer-run/sprinter/0006.png differ diff --git a/applications/Volkov/artifacts/mame-p5/viewer/clean-dss-return.png b/applications/Volkov/artifacts/mame-p5/viewer/clean-dss-return.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/viewer/clean-dss-return.png differ diff --git a/applications/Volkov/artifacts/mame-p5/viewer/commander-restored.png b/applications/Volkov/artifacts/mame-p5/viewer/commander-restored.png new file mode 100644 index 0000000..c6f1929 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/viewer/commander-restored.png differ diff --git a/applications/Volkov/artifacts/mame-p5/viewer/mdview-main.png b/applications/Volkov/artifacts/mame-p5/viewer/mdview-main.png new file mode 100644 index 0000000..e99e281 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/viewer/mdview-main.png differ diff --git a/applications/Volkov/artifacts/mame-p5/viewer/mdview-pagedown.png b/applications/Volkov/artifacts/mame-p5/viewer/mdview-pagedown.png new file mode 100644 index 0000000..2140158 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/viewer/mdview-pagedown.png differ diff --git a/applications/Volkov/artifacts/mame-p5/viewer/mdview-raw.png b/applications/Volkov/artifacts/mame-p5/viewer/mdview-raw.png new file mode 100644 index 0000000..b13058f Binary files /dev/null and b/applications/Volkov/artifacts/mame-p5/viewer/mdview-raw.png differ diff --git a/applications/Volkov/artifacts/mame-p6/sort-final/sprinter/0000.png b/applications/Volkov/artifacts/mame-p6/sort-final/sprinter/0000.png new file mode 100644 index 0000000..c468ad7 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p6/sort-final/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p6/sort-final/sprinter/0001.png b/applications/Volkov/artifacts/mame-p6/sort-final/sprinter/0001.png new file mode 100644 index 0000000..9fa4db6 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p6/sort-final/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p6/sort-final/sprinter/0002.png b/applications/Volkov/artifacts/mame-p6/sort-final/sprinter/0002.png new file mode 100644 index 0000000..4d43936 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p6/sort-final/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p6/sort-final/sprinter/0003.png b/applications/Volkov/artifacts/mame-p6/sort-final/sprinter/0003.png new file mode 100644 index 0000000..8593675 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p6/sort-final/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p6/sort-final/sprinter/0004.png b/applications/Volkov/artifacts/mame-p6/sort-final/sprinter/0004.png new file mode 100644 index 0000000..a470121 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p6/sort-final/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p6/sort-final/sprinter/0005.png b/applications/Volkov/artifacts/mame-p6/sort-final/sprinter/0005.png new file mode 100644 index 0000000..ee0350c Binary files /dev/null and b/applications/Volkov/artifacts/mame-p6/sort-final/sprinter/0005.png differ diff --git a/applications/Volkov/artifacts/mame-p6/sort-final/sprinter/0006.png b/applications/Volkov/artifacts/mame-p6/sort-final/sprinter/0006.png new file mode 100644 index 0000000..904a45e Binary files /dev/null and b/applications/Volkov/artifacts/mame-p6/sort-final/sprinter/0006.png differ diff --git a/applications/Volkov/artifacts/mame-p6/sort-final/sprinter/0007.png b/applications/Volkov/artifacts/mame-p6/sort-final/sprinter/0007.png new file mode 100644 index 0000000..c492657 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p6/sort-final/sprinter/0007.png differ diff --git a/applications/Volkov/artifacts/mame-p6/sort-final/sprinter/0008.png b/applications/Volkov/artifacts/mame-p6/sort-final/sprinter/0008.png new file mode 100644 index 0000000..ee59227 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p6/sort-final/sprinter/0008.png differ diff --git a/applications/Volkov/artifacts/mame-p6/sort-final/sprinter/0009.png b/applications/Volkov/artifacts/mame-p6/sort-final/sprinter/0009.png new file mode 100644 index 0000000..091349b Binary files /dev/null and b/applications/Volkov/artifacts/mame-p6/sort-final/sprinter/0009.png differ diff --git a/applications/Volkov/artifacts/mame-p6/sort-final/sprinter/0010.png b/applications/Volkov/artifacts/mame-p6/sort-final/sprinter/0010.png new file mode 100644 index 0000000..cb6fa5a Binary files /dev/null and b/applications/Volkov/artifacts/mame-p6/sort-final/sprinter/0010.png differ diff --git a/applications/Volkov/artifacts/mame-p6/sort-final/sprinter/0011.png b/applications/Volkov/artifacts/mame-p6/sort-final/sprinter/0011.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p6/sort-final/sprinter/0011.png differ diff --git a/applications/Volkov/artifacts/mame-p6/sort-run/sprinter/0000.png b/applications/Volkov/artifacts/mame-p6/sort-run/sprinter/0000.png new file mode 100644 index 0000000..c468ad7 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p6/sort-run/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p6/sort-run/sprinter/0001.png b/applications/Volkov/artifacts/mame-p6/sort-run/sprinter/0001.png new file mode 100644 index 0000000..9fa4db6 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p6/sort-run/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p6/sort-run/sprinter/0002.png b/applications/Volkov/artifacts/mame-p6/sort-run/sprinter/0002.png new file mode 100644 index 0000000..4d43936 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p6/sort-run/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p6/sort-run/sprinter/0003.png b/applications/Volkov/artifacts/mame-p6/sort-run/sprinter/0003.png new file mode 100644 index 0000000..8593675 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p6/sort-run/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p6/sort-run/sprinter/0004.png b/applications/Volkov/artifacts/mame-p6/sort-run/sprinter/0004.png new file mode 100644 index 0000000..a470121 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p6/sort-run/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p6/sort-run/sprinter/0005.png b/applications/Volkov/artifacts/mame-p6/sort-run/sprinter/0005.png new file mode 100644 index 0000000..ee0350c Binary files /dev/null and b/applications/Volkov/artifacts/mame-p6/sort-run/sprinter/0005.png differ diff --git a/applications/Volkov/artifacts/mame-p6/sort-run/sprinter/0006.png b/applications/Volkov/artifacts/mame-p6/sort-run/sprinter/0006.png new file mode 100644 index 0000000..904a45e Binary files /dev/null and b/applications/Volkov/artifacts/mame-p6/sort-run/sprinter/0006.png differ diff --git a/applications/Volkov/artifacts/mame-p6/sort-run/sprinter/0007.png b/applications/Volkov/artifacts/mame-p6/sort-run/sprinter/0007.png new file mode 100644 index 0000000..c492657 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p6/sort-run/sprinter/0007.png differ diff --git a/applications/Volkov/artifacts/mame-p6/sort-run/sprinter/0008.png b/applications/Volkov/artifacts/mame-p6/sort-run/sprinter/0008.png new file mode 100644 index 0000000..ee59227 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p6/sort-run/sprinter/0008.png differ diff --git a/applications/Volkov/artifacts/mame-p6/sort-run/sprinter/0009.png b/applications/Volkov/artifacts/mame-p6/sort-run/sprinter/0009.png new file mode 100644 index 0000000..091349b Binary files /dev/null and b/applications/Volkov/artifacts/mame-p6/sort-run/sprinter/0009.png differ diff --git a/applications/Volkov/artifacts/mame-p6/sort-run/sprinter/0010.png b/applications/Volkov/artifacts/mame-p6/sort-run/sprinter/0010.png new file mode 100644 index 0000000..cb6fa5a Binary files /dev/null and b/applications/Volkov/artifacts/mame-p6/sort-run/sprinter/0010.png differ diff --git a/applications/Volkov/artifacts/mame-p6/sort-run/sprinter/0011.png b/applications/Volkov/artifacts/mame-p6/sort-run/sprinter/0011.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p6/sort-run/sprinter/0011.png differ diff --git a/applications/Volkov/artifacts/mame-p6/sort/clean-dss-return.png b/applications/Volkov/artifacts/mame-p6/sort/clean-dss-return.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p6/sort/clean-dss-return.png differ diff --git a/applications/Volkov/artifacts/mame-p6/sort/date-ascending.png b/applications/Volkov/artifacts/mame-p6/sort/date-ascending.png new file mode 100644 index 0000000..ee0350c Binary files /dev/null and b/applications/Volkov/artifacts/mame-p6/sort/date-ascending.png differ diff --git a/applications/Volkov/artifacts/mame-p6/sort/date-descending.png b/applications/Volkov/artifacts/mame-p6/sort/date-descending.png new file mode 100644 index 0000000..904a45e Binary files /dev/null and b/applications/Volkov/artifacts/mame-p6/sort/date-descending.png differ diff --git a/applications/Volkov/artifacts/mame-p6/sort/extension-ascending.png b/applications/Volkov/artifacts/mame-p6/sort/extension-ascending.png new file mode 100644 index 0000000..9fa4db6 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p6/sort/extension-ascending.png differ diff --git a/applications/Volkov/artifacts/mame-p6/sort/extension-descending.png b/applications/Volkov/artifacts/mame-p6/sort/extension-descending.png new file mode 100644 index 0000000..4d43936 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p6/sort/extension-descending.png differ diff --git a/applications/Volkov/artifacts/mame-p6/sort/independent-panels.png b/applications/Volkov/artifacts/mame-p6/sort/independent-panels.png new file mode 100644 index 0000000..091349b Binary files /dev/null and b/applications/Volkov/artifacts/mame-p6/sort/independent-panels.png differ diff --git a/applications/Volkov/artifacts/mame-p6/sort/name-ascending.png b/applications/Volkov/artifacts/mame-p6/sort/name-ascending.png new file mode 100644 index 0000000..c468ad7 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p6/sort/name-ascending.png differ diff --git a/applications/Volkov/artifacts/mame-p6/sort/name-descending.png b/applications/Volkov/artifacts/mame-p6/sort/name-descending.png new file mode 100644 index 0000000..ee59227 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p6/sort/name-descending.png differ diff --git a/applications/Volkov/artifacts/mame-p6/sort/name-reset-ascending.png b/applications/Volkov/artifacts/mame-p6/sort/name-reset-ascending.png new file mode 100644 index 0000000..c492657 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p6/sort/name-reset-ascending.png differ diff --git a/applications/Volkov/artifacts/mame-p6/sort/refresh-keeps-mode.png b/applications/Volkov/artifacts/mame-p6/sort/refresh-keeps-mode.png new file mode 100644 index 0000000..cb6fa5a Binary files /dev/null and b/applications/Volkov/artifacts/mame-p6/sort/refresh-keeps-mode.png differ diff --git a/applications/Volkov/artifacts/mame-p6/sort/size-ascending.png b/applications/Volkov/artifacts/mame-p6/sort/size-ascending.png new file mode 100644 index 0000000..8593675 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p6/sort/size-ascending.png differ diff --git a/applications/Volkov/artifacts/mame-p6/sort/size-descending.png b/applications/Volkov/artifacts/mame-p6/sort/size-descending.png new file mode 100644 index 0000000..a470121 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p6/sort/size-descending.png differ diff --git a/applications/Volkov/artifacts/mame-p7/select-final/sprinter/0000.png b/applications/Volkov/artifacts/mame-p7/select-final/sprinter/0000.png new file mode 100644 index 0000000..e9a4b88 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p7/select-final/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p7/select-final/sprinter/0001.png b/applications/Volkov/artifacts/mame-p7/select-final/sprinter/0001.png new file mode 100644 index 0000000..05ff151 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p7/select-final/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p7/select-final/sprinter/0002.png b/applications/Volkov/artifacts/mame-p7/select-final/sprinter/0002.png new file mode 100644 index 0000000..a80968a Binary files /dev/null and b/applications/Volkov/artifacts/mame-p7/select-final/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p7/select-final/sprinter/0003.png b/applications/Volkov/artifacts/mame-p7/select-final/sprinter/0003.png new file mode 100644 index 0000000..c4d88dd Binary files /dev/null and b/applications/Volkov/artifacts/mame-p7/select-final/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p7/select-final/sprinter/0004.png b/applications/Volkov/artifacts/mame-p7/select-final/sprinter/0004.png new file mode 100644 index 0000000..7c2056c Binary files /dev/null and b/applications/Volkov/artifacts/mame-p7/select-final/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p7/select-final/sprinter/0005.png b/applications/Volkov/artifacts/mame-p7/select-final/sprinter/0005.png new file mode 100644 index 0000000..41d2986 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p7/select-final/sprinter/0005.png differ diff --git a/applications/Volkov/artifacts/mame-p7/select-final/sprinter/0006.png b/applications/Volkov/artifacts/mame-p7/select-final/sprinter/0006.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p7/select-final/sprinter/0006.png differ diff --git a/applications/Volkov/artifacts/mame-p7/select-regression-files-only/sprinter/0000.png b/applications/Volkov/artifacts/mame-p7/select-regression-files-only/sprinter/0000.png new file mode 100644 index 0000000..2ceefa9 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p7/select-regression-files-only/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p7/select-regression-files-only/sprinter/0001.png b/applications/Volkov/artifacts/mame-p7/select-regression-files-only/sprinter/0001.png new file mode 100644 index 0000000..e661562 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p7/select-regression-files-only/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p7/select-regression-files-only/sprinter/0002.png b/applications/Volkov/artifacts/mame-p7/select-regression-files-only/sprinter/0002.png new file mode 100644 index 0000000..d6624f3 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p7/select-regression-files-only/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p7/select-regression-files-only/sprinter/0003.png b/applications/Volkov/artifacts/mame-p7/select-regression-files-only/sprinter/0003.png new file mode 100644 index 0000000..e87d078 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p7/select-regression-files-only/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p7/select-regression-files-only/sprinter/0004.png b/applications/Volkov/artifacts/mame-p7/select-regression-files-only/sprinter/0004.png new file mode 100644 index 0000000..e906edd Binary files /dev/null and b/applications/Volkov/artifacts/mame-p7/select-regression-files-only/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p7/select-regression-files-only/sprinter/0005.png b/applications/Volkov/artifacts/mame-p7/select-regression-files-only/sprinter/0005.png new file mode 100644 index 0000000..0d7f85f Binary files /dev/null and b/applications/Volkov/artifacts/mame-p7/select-regression-files-only/sprinter/0005.png differ diff --git a/applications/Volkov/artifacts/mame-p7/select-regression-files-only/sprinter/0006.png b/applications/Volkov/artifacts/mame-p7/select-regression-files-only/sprinter/0006.png new file mode 100644 index 0000000..50b853f Binary files /dev/null and b/applications/Volkov/artifacts/mame-p7/select-regression-files-only/sprinter/0006.png differ diff --git a/applications/Volkov/artifacts/mame-p7/select-regression-files-only/sprinter/0007.png b/applications/Volkov/artifacts/mame-p7/select-regression-files-only/sprinter/0007.png new file mode 100644 index 0000000..31220ad Binary files /dev/null and b/applications/Volkov/artifacts/mame-p7/select-regression-files-only/sprinter/0007.png differ diff --git a/applications/Volkov/artifacts/mame-p7/select-regression-files-only/sprinter/0008.png b/applications/Volkov/artifacts/mame-p7/select-regression-files-only/sprinter/0008.png new file mode 100644 index 0000000..689feeb Binary files /dev/null and b/applications/Volkov/artifacts/mame-p7/select-regression-files-only/sprinter/0008.png differ diff --git a/applications/Volkov/artifacts/mame-p7/select-regression-files-only/sprinter/0009.png b/applications/Volkov/artifacts/mame-p7/select-regression-files-only/sprinter/0009.png new file mode 100644 index 0000000..32370bc Binary files /dev/null and b/applications/Volkov/artifacts/mame-p7/select-regression-files-only/sprinter/0009.png differ diff --git a/applications/Volkov/artifacts/mame-p7/select-regression-files-only/sprinter/0010.png b/applications/Volkov/artifacts/mame-p7/select-regression-files-only/sprinter/0010.png new file mode 100644 index 0000000..b80da54 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p7/select-regression-files-only/sprinter/0010.png differ diff --git a/applications/Volkov/artifacts/mame-p7/select-regression-files-only/sprinter/0011.png b/applications/Volkov/artifacts/mame-p7/select-regression-files-only/sprinter/0011.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p7/select-regression-files-only/sprinter/0011.png differ diff --git a/applications/Volkov/artifacts/mame-p7/select-run/sprinter/0000.png b/applications/Volkov/artifacts/mame-p7/select-run/sprinter/0000.png new file mode 100644 index 0000000..89b5728 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p7/select-run/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p7/select-run/sprinter/0001.png b/applications/Volkov/artifacts/mame-p7/select-run/sprinter/0001.png new file mode 100644 index 0000000..1824d88 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p7/select-run/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p7/select-run/sprinter/0002.png b/applications/Volkov/artifacts/mame-p7/select-run/sprinter/0002.png new file mode 100644 index 0000000..de40223 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p7/select-run/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p7/select-run/sprinter/0003.png b/applications/Volkov/artifacts/mame-p7/select-run/sprinter/0003.png new file mode 100644 index 0000000..e191823 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p7/select-run/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p7/select-run/sprinter/0004.png b/applications/Volkov/artifacts/mame-p7/select-run/sprinter/0004.png new file mode 100644 index 0000000..8176ab4 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p7/select-run/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p7/select-run/sprinter/0005.png b/applications/Volkov/artifacts/mame-p7/select-run/sprinter/0005.png new file mode 100644 index 0000000..1b06831 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p7/select-run/sprinter/0005.png differ diff --git a/applications/Volkov/artifacts/mame-p7/select-run/sprinter/0006.png b/applications/Volkov/artifacts/mame-p7/select-run/sprinter/0006.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p7/select-run/sprinter/0006.png differ diff --git a/applications/Volkov/artifacts/mame-p7/select/clean-dss-return.png b/applications/Volkov/artifacts/mame-p7/select/clean-dss-return.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p7/select/clean-dss-return.png differ diff --git a/applications/Volkov/artifacts/mame-p7/select/insert-three.png b/applications/Volkov/artifacts/mame-p7/select/insert-three.png new file mode 100644 index 0000000..a80968a Binary files /dev/null and b/applications/Volkov/artifacts/mame-p7/select/insert-three.png differ diff --git a/applications/Volkov/artifacts/mame-p7/select/inverted.png b/applications/Volkov/artifacts/mame-p7/select/inverted.png new file mode 100644 index 0000000..e906edd Binary files /dev/null and b/applications/Volkov/artifacts/mame-p7/select/inverted.png differ diff --git a/applications/Volkov/artifacts/mame-p7/select/refresh-clears.png b/applications/Volkov/artifacts/mame-p7/select/refresh-clears.png new file mode 100644 index 0000000..41d2986 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p7/select/refresh-clears.png differ diff --git a/applications/Volkov/artifacts/mame-p7/select/sort-keeps-selection.png b/applications/Volkov/artifacts/mame-p7/select/sort-keeps-selection.png new file mode 100644 index 0000000..c4d88dd Binary files /dev/null and b/applications/Volkov/artifacts/mame-p7/select/sort-keeps-selection.png differ diff --git a/applications/Volkov/artifacts/mame-p7/select/virtual-entry-guard.png b/applications/Volkov/artifacts/mame-p7/select/virtual-entry-guard.png new file mode 100644 index 0000000..05ff151 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p7/select/virtual-entry-guard.png differ diff --git a/applications/Volkov/artifacts/mame-p8/masks-run/sprinter/0000.png b/applications/Volkov/artifacts/mame-p8/masks-run/sprinter/0000.png new file mode 100644 index 0000000..77da0e5 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p8/masks-run/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p8/masks-run/sprinter/0001.png b/applications/Volkov/artifacts/mame-p8/masks-run/sprinter/0001.png new file mode 100644 index 0000000..183ceeb Binary files /dev/null and b/applications/Volkov/artifacts/mame-p8/masks-run/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p8/masks-run/sprinter/0002.png b/applications/Volkov/artifacts/mame-p8/masks-run/sprinter/0002.png new file mode 100644 index 0000000..9da86d6 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p8/masks-run/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p8/masks-run/sprinter/0003.png b/applications/Volkov/artifacts/mame-p8/masks-run/sprinter/0003.png new file mode 100644 index 0000000..9d42acd Binary files /dev/null and b/applications/Volkov/artifacts/mame-p8/masks-run/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p8/masks-run/sprinter/0004.png b/applications/Volkov/artifacts/mame-p8/masks-run/sprinter/0004.png new file mode 100644 index 0000000..ac7cb26 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p8/masks-run/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p8/masks-run/sprinter/0005.png b/applications/Volkov/artifacts/mame-p8/masks-run/sprinter/0005.png new file mode 100644 index 0000000..81d0fc7 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p8/masks-run/sprinter/0005.png differ diff --git a/applications/Volkov/artifacts/mame-p8/masks-run/sprinter/0006.png b/applications/Volkov/artifacts/mame-p8/masks-run/sprinter/0006.png new file mode 100644 index 0000000..60fd542 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p8/masks-run/sprinter/0006.png differ diff --git a/applications/Volkov/artifacts/mame-p8/masks-run/sprinter/0007.png b/applications/Volkov/artifacts/mame-p8/masks-run/sprinter/0007.png new file mode 100644 index 0000000..63dcd25 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p8/masks-run/sprinter/0007.png differ diff --git a/applications/Volkov/artifacts/mame-p8/masks-run/sprinter/0008.png b/applications/Volkov/artifacts/mame-p8/masks-run/sprinter/0008.png new file mode 100644 index 0000000..011fb04 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p8/masks-run/sprinter/0008.png differ diff --git a/applications/Volkov/artifacts/mame-p8/masks-run/sprinter/0009.png b/applications/Volkov/artifacts/mame-p8/masks-run/sprinter/0009.png new file mode 100644 index 0000000..cbb846d Binary files /dev/null and b/applications/Volkov/artifacts/mame-p8/masks-run/sprinter/0009.png differ diff --git a/applications/Volkov/artifacts/mame-p8/masks-run/sprinter/0010.png b/applications/Volkov/artifacts/mame-p8/masks-run/sprinter/0010.png new file mode 100644 index 0000000..9a7ac8e Binary files /dev/null and b/applications/Volkov/artifacts/mame-p8/masks-run/sprinter/0010.png differ diff --git a/applications/Volkov/artifacts/mame-p8/masks-run/sprinter/0011.png b/applications/Volkov/artifacts/mame-p8/masks-run/sprinter/0011.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p8/masks-run/sprinter/0011.png differ diff --git a/applications/Volkov/artifacts/mame-p8/masks/b-unselected.png b/applications/Volkov/artifacts/mame-p8/masks/b-unselected.png new file mode 100644 index 0000000..cbb846d Binary files /dev/null and b/applications/Volkov/artifacts/mame-p8/masks/b-unselected.png differ diff --git a/applications/Volkov/artifacts/mame-p8/masks/bin-selected.png b/applications/Volkov/artifacts/mame-p8/masks/bin-selected.png new file mode 100644 index 0000000..011fb04 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p8/masks/bin-selected.png differ diff --git a/applications/Volkov/artifacts/mame-p8/masks/cancel-preserves.png b/applications/Volkov/artifacts/mame-p8/masks/cancel-preserves.png new file mode 100644 index 0000000..9a7ac8e Binary files /dev/null and b/applications/Volkov/artifacts/mame-p8/masks/cancel-preserves.png differ diff --git a/applications/Volkov/artifacts/mame-p8/masks/clean-dss-return.png b/applications/Volkov/artifacts/mame-p8/masks/clean-dss-return.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p8/masks/clean-dss-return.png differ diff --git a/applications/Volkov/artifacts/mame-p8/masks/default-dialog.png b/applications/Volkov/artifacts/mame-p8/masks/default-dialog.png new file mode 100644 index 0000000..60fd542 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p8/masks/default-dialog.png differ diff --git a/applications/Volkov/artifacts/mame-p8/masks/edited-bin-mask.png b/applications/Volkov/artifacts/mame-p8/masks/edited-bin-mask.png new file mode 100644 index 0000000..63dcd25 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p8/masks/edited-bin-mask.png differ diff --git a/applications/Volkov/artifacts/mame-p9/mkdir-run/sprinter/0000.png b/applications/Volkov/artifacts/mame-p9/mkdir-run/sprinter/0000.png new file mode 100644 index 0000000..ea14768 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p9/mkdir-run/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-p9/mkdir-run/sprinter/0001.png b/applications/Volkov/artifacts/mame-p9/mkdir-run/sprinter/0001.png new file mode 100644 index 0000000..e64dcf4 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p9/mkdir-run/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-p9/mkdir-run/sprinter/0002.png b/applications/Volkov/artifacts/mame-p9/mkdir-run/sprinter/0002.png new file mode 100644 index 0000000..b10bf0f Binary files /dev/null and b/applications/Volkov/artifacts/mame-p9/mkdir-run/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-p9/mkdir-run/sprinter/0003.png b/applications/Volkov/artifacts/mame-p9/mkdir-run/sprinter/0003.png new file mode 100644 index 0000000..5da1402 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p9/mkdir-run/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-p9/mkdir-run/sprinter/0004.png b/applications/Volkov/artifacts/mame-p9/mkdir-run/sprinter/0004.png new file mode 100644 index 0000000..3685e64 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p9/mkdir-run/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-p9/mkdir-run/sprinter/0005.png b/applications/Volkov/artifacts/mame-p9/mkdir-run/sprinter/0005.png new file mode 100644 index 0000000..9dd7b05 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p9/mkdir-run/sprinter/0005.png differ diff --git a/applications/Volkov/artifacts/mame-p9/mkdir-run/sprinter/0006.png b/applications/Volkov/artifacts/mame-p9/mkdir-run/sprinter/0006.png new file mode 100644 index 0000000..ac23190 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p9/mkdir-run/sprinter/0006.png differ diff --git a/applications/Volkov/artifacts/mame-p9/mkdir-run/sprinter/0007.png b/applications/Volkov/artifacts/mame-p9/mkdir-run/sprinter/0007.png new file mode 100644 index 0000000..8b2b3ab Binary files /dev/null and b/applications/Volkov/artifacts/mame-p9/mkdir-run/sprinter/0007.png differ diff --git a/applications/Volkov/artifacts/mame-p9/mkdir-run/sprinter/0008.png b/applications/Volkov/artifacts/mame-p9/mkdir-run/sprinter/0008.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p9/mkdir-run/sprinter/0008.png differ diff --git a/applications/Volkov/artifacts/mame-p9/mkdir/clean-dss-return.png b/applications/Volkov/artifacts/mame-p9/mkdir/clean-dss-return.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p9/mkdir/clean-dss-return.png differ diff --git a/applications/Volkov/artifacts/mame-p9/mkdir/created.png b/applications/Volkov/artifacts/mame-p9/mkdir/created.png new file mode 100644 index 0000000..b10bf0f Binary files /dev/null and b/applications/Volkov/artifacts/mame-p9/mkdir/created.png differ diff --git a/applications/Volkov/artifacts/mame-p9/mkdir/dialog-empty.png b/applications/Volkov/artifacts/mame-p9/mkdir/dialog-empty.png new file mode 100644 index 0000000..e64dcf4 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p9/mkdir/dialog-empty.png differ diff --git a/applications/Volkov/artifacts/mame-p9/mkdir/duplicate-error.png b/applications/Volkov/artifacts/mame-p9/mkdir/duplicate-error.png new file mode 100644 index 0000000..5da1402 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p9/mkdir/duplicate-error.png differ diff --git a/applications/Volkov/artifacts/mame-p9/mkdir/entered-newdir.png b/applications/Volkov/artifacts/mame-p9/mkdir/entered-newdir.png new file mode 100644 index 0000000..ac23190 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p9/mkdir/entered-newdir.png differ diff --git a/applications/Volkov/artifacts/mame-p9/mkdir/invalid-name.png b/applications/Volkov/artifacts/mame-p9/mkdir/invalid-name.png new file mode 100644 index 0000000..3685e64 Binary files /dev/null and b/applications/Volkov/artifacts/mame-p9/mkdir/invalid-name.png differ diff --git a/applications/Volkov/artifacts/mame-ui/selected-footer-clean.png b/applications/Volkov/artifacts/mame-ui/selected-footer-clean.png new file mode 100644 index 0000000..4526c99 Binary files /dev/null and b/applications/Volkov/artifacts/mame-ui/selected-footer-clean.png differ diff --git a/applications/Volkov/artifacts/mame-ui/vc-footer/sprinter/0000.png b/applications/Volkov/artifacts/mame-ui/vc-footer/sprinter/0000.png new file mode 100644 index 0000000..2ceefa9 Binary files /dev/null and b/applications/Volkov/artifacts/mame-ui/vc-footer/sprinter/0000.png differ diff --git a/applications/Volkov/artifacts/mame-ui/vc-footer/sprinter/0001.png b/applications/Volkov/artifacts/mame-ui/vc-footer/sprinter/0001.png new file mode 100644 index 0000000..e661562 Binary files /dev/null and b/applications/Volkov/artifacts/mame-ui/vc-footer/sprinter/0001.png differ diff --git a/applications/Volkov/artifacts/mame-ui/vc-footer/sprinter/0002.png b/applications/Volkov/artifacts/mame-ui/vc-footer/sprinter/0002.png new file mode 100644 index 0000000..f9fe8fa Binary files /dev/null and b/applications/Volkov/artifacts/mame-ui/vc-footer/sprinter/0002.png differ diff --git a/applications/Volkov/artifacts/mame-ui/vc-footer/sprinter/0003.png b/applications/Volkov/artifacts/mame-ui/vc-footer/sprinter/0003.png new file mode 100644 index 0000000..0349009 Binary files /dev/null and b/applications/Volkov/artifacts/mame-ui/vc-footer/sprinter/0003.png differ diff --git a/applications/Volkov/artifacts/mame-ui/vc-footer/sprinter/0004.png b/applications/Volkov/artifacts/mame-ui/vc-footer/sprinter/0004.png new file mode 100644 index 0000000..1b20ed3 Binary files /dev/null and b/applications/Volkov/artifacts/mame-ui/vc-footer/sprinter/0004.png differ diff --git a/applications/Volkov/artifacts/mame-ui/vc-footer/sprinter/0005.png b/applications/Volkov/artifacts/mame-ui/vc-footer/sprinter/0005.png new file mode 100644 index 0000000..c43b1ed Binary files /dev/null and b/applications/Volkov/artifacts/mame-ui/vc-footer/sprinter/0005.png differ diff --git a/applications/Volkov/artifacts/mame-ui/vc-footer/sprinter/0006.png b/applications/Volkov/artifacts/mame-ui/vc-footer/sprinter/0006.png new file mode 100644 index 0000000..9929f6e Binary files /dev/null and b/applications/Volkov/artifacts/mame-ui/vc-footer/sprinter/0006.png differ diff --git a/applications/Volkov/artifacts/mame-ui/vc-footer/sprinter/0007.png b/applications/Volkov/artifacts/mame-ui/vc-footer/sprinter/0007.png new file mode 100644 index 0000000..3ea0119 Binary files /dev/null and b/applications/Volkov/artifacts/mame-ui/vc-footer/sprinter/0007.png differ diff --git a/applications/Volkov/artifacts/mame-ui/vc-footer/sprinter/0008.png b/applications/Volkov/artifacts/mame-ui/vc-footer/sprinter/0008.png new file mode 100644 index 0000000..5f2b8a5 Binary files /dev/null and b/applications/Volkov/artifacts/mame-ui/vc-footer/sprinter/0008.png differ diff --git a/applications/Volkov/artifacts/mame-ui/vc-footer/sprinter/0009.png b/applications/Volkov/artifacts/mame-ui/vc-footer/sprinter/0009.png new file mode 100644 index 0000000..4e60fc5 Binary files /dev/null and b/applications/Volkov/artifacts/mame-ui/vc-footer/sprinter/0009.png differ diff --git a/applications/Volkov/artifacts/mame-ui/vc-footer/sprinter/0010.png b/applications/Volkov/artifacts/mame-ui/vc-footer/sprinter/0010.png new file mode 100644 index 0000000..4b863b7 Binary files /dev/null and b/applications/Volkov/artifacts/mame-ui/vc-footer/sprinter/0010.png differ diff --git a/applications/Volkov/artifacts/mame-ui/vc-footer/sprinter/0011.png b/applications/Volkov/artifacts/mame-ui/vc-footer/sprinter/0011.png new file mode 100644 index 0000000..8f9b5c2 Binary files /dev/null and b/applications/Volkov/artifacts/mame-ui/vc-footer/sprinter/0011.png differ diff --git a/applications/Volkov/docs/commander-architecture.md b/applications/Volkov/docs/commander-architecture.md new file mode 100644 index 0000000..8e5a5eb --- /dev/null +++ b/applications/Volkov/docs/commander-architecture.md @@ -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 `0x0000–0x3FFF` — DSS/BIOS; +- W1 `0x4000–0x7FFF` — переключаемые банки кода; +- W2 `0x8000–0xBFFF` — HOME, DATA/BSS, heap и стек; +- W3 `0xC000–0xFFFF` — 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 + +Указатель в диапазон `0xC000–0xFFFF` действителен только внутри функции, +которая явно владеет отображением 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 + области панели. +- Смена каталога перерисовывает соответствующую половину и общие строки + 29–31. +- Полный `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. diff --git a/applications/Volkov/docs/commander-keymap.md b/applications/Volkov/docs/commander-keymap.md new file mode 100644 index 0000000..f81ebf3 --- /dev/null +++ b/applications/Volkov/docs/commander-keymap.md @@ -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-коды + +Значения уже объявлены в ``: + +| Клавиша | 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 `0x01–0x1A` без +надёжного сохранения состояния 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. diff --git a/applications/Volkov/docs/commander-requirements.md b/applications/Volkov/docs/commander-requirements.md new file mode 100644 index 0000000..699087d --- /dev/null +++ b/applications/Volkov/docs/commander-requirements.md @@ -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.** Прикладной код не хранит указатели в диапазон + `0xC000–0xFFFF` между вызовами хранилища. +- **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. diff --git a/applications/Volkov/docs/commander-roadmap.md b/applications/Volkov/docs/commander-roadmap.md new file mode 100644 index 0000000..0717a46 --- /dev/null +++ b/applications/Volkov/docs/commander-roadmap.md @@ -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 | 4–5 дней | +| 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 не расширялись +неуправляемо. diff --git a/applications/Volkov/docs/dss-large-directories.md b/applications/Volkov/docs/dss-large-directories.md new file mode 100644 index 0000000..17292ef --- /dev/null +++ b/applications/Volkov/docs/dss-large-directories.md @@ -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.1–1.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`. diff --git a/applications/Volkov/docs/p1-platform-probes.md b/applications/Volkov/docs/p1-platform-probes.md new file mode 100644 index 0000000..8819a85 --- /dev/null +++ b/applications/Volkov/docs/p1-platform-probes.md @@ -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; +- F1–F10; +- 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. diff --git a/applications/Volkov/docs/p10-rename-results.md b/applications/Volkov/docs/p10-rename-results.md new file mode 100644 index 0000000..6d1ead5 --- /dev/null +++ b/applications/Volkov/docs/p10-rename-results.md @@ -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 +``` diff --git a/applications/Volkov/docs/p11-delete-results.md b/applications/Volkov/docs/p11-delete-results.md new file mode 100644 index 0000000..f354f7e --- /dev/null +++ b/applications/Volkov/docs/p11-delete-results.md @@ -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 +``` diff --git a/applications/Volkov/docs/p12-group-copy-results.md b/applications/Volkov/docs/p12-group-copy-results.md new file mode 100644 index 0000000..ee74acb --- /dev/null +++ b/applications/Volkov/docs/p12-group-copy-results.md @@ -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 +``` diff --git a/applications/Volkov/docs/p13-group-delete-results.md b/applications/Volkov/docs/p13-group-delete-results.md new file mode 100644 index 0000000..8f11f44 --- /dev/null +++ b/applications/Volkov/docs/p13-group-delete-results.md @@ -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 +``` diff --git a/applications/Volkov/docs/p14-default-colors-results.md b/applications/Volkov/docs/p14-default-colors-results.md new file mode 100644 index 0000000..77cc493 --- /dev/null +++ b/applications/Volkov/docs/p14-default-colors-results.md @@ -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 +``` diff --git a/applications/Volkov/docs/p15-tree-copy-results.md b/applications/Volkov/docs/p15-tree-copy-results.md new file mode 100644 index 0000000..033d6a0 --- /dev/null +++ b/applications/Volkov/docs/p15-tree-copy-results.md @@ -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 +``` diff --git a/applications/Volkov/docs/p16-group-tree-copy-results.md b/applications/Volkov/docs/p16-group-tree-copy-results.md new file mode 100644 index 0000000..8a2714b --- /dev/null +++ b/applications/Volkov/docs/p16-group-tree-copy-results.md @@ -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 +``` diff --git a/applications/Volkov/docs/p17-tree-delete-results.md b/applications/Volkov/docs/p17-tree-delete-results.md new file mode 100644 index 0000000..e2cd426 --- /dev/null +++ b/applications/Volkov/docs/p17-tree-delete-results.md @@ -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 +``` diff --git a/applications/Volkov/docs/p18-metadata-results.md b/applications/Volkov/docs/p18-metadata-results.md new file mode 100644 index 0000000..94e583f --- /dev/null +++ b/applications/Volkov/docs/p18-metadata-results.md @@ -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 +``` diff --git a/applications/Volkov/docs/p19-copy-policy-results.md b/applications/Volkov/docs/p19-copy-policy-results.md new file mode 100644 index 0000000..19be7ad --- /dev/null +++ b/applications/Volkov/docs/p19-copy-policy-results.md @@ -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 +``` diff --git a/applications/Volkov/docs/p2-palette-results.md b/applications/Volkov/docs/p2-palette-results.md new file mode 100644 index 0000000..c5bd447 --- /dev/null +++ b/applications/Volkov/docs/p2-palette-results.md @@ -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 требуется проверить те же цвета и возврат исходной палитры после +выхода. diff --git a/applications/Volkov/docs/p2-screen-backend.md b/applications/Volkov/docs/p2-screen-backend.md new file mode 100644 index 0000000..522ae87 --- /dev/null +++ b/applications/Volkov/docs/p2-screen-backend.md @@ -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. diff --git a/applications/Volkov/docs/p2-screen-probe-results.md b/applications/Volkov/docs/p2-screen-probe-results.md new file mode 100644 index 0000000..0d29213 --- /dev/null +++ b/applications/Volkov/docs/p2-screen-probe-results.md @@ -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. diff --git a/applications/Volkov/docs/p2-skeleton-results.md b/applications/Volkov/docs/p2-skeleton-results.md new file mode 100644 index 0000000..d524927 --- /dev/null +++ b/applications/Volkov/docs/p2-skeleton-results.md @@ -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` | правая панель показывает строки 002–028, 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: заменить синтетические строки настоящими независимыми каталогами. diff --git a/applications/Volkov/docs/p3-panel-results.md b/applications/Volkov/docs/p3-panel-results.md new file mode 100644 index 0000000..0c88c2d --- /dev/null +++ b/applications/Volkov/docs/p3-panel-results.md @@ -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 показывает `` | [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. diff --git a/applications/Volkov/docs/p4-copy-results.md b/applications/Volkov/docs/p4-copy-results.md new file mode 100644 index 0000000..b9cf986 --- /dev/null +++ b/applications/Volkov/docs/p4-copy-results.md @@ -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. diff --git a/applications/Volkov/docs/p4-exec-results.md b/applications/Volkov/docs/p4-exec-results.md new file mode 100644 index 0000000..22f040b --- /dev/null +++ b/applications/Volkov/docs/p4-exec-results.md @@ -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. diff --git a/applications/Volkov/docs/p5-stability-results.md b/applications/Volkov/docs/p5-stability-results.md new file mode 100644 index 0000000..8f9fbd9 --- /dev/null +++ b/applications/Volkov/docs/p5-stability-results.md @@ -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. diff --git a/applications/Volkov/docs/p6-sort-results.md b/applications/Volkov/docs/p6-sort-results.md new file mode 100644 index 0000000..7e9ed49 --- /dev/null +++ b/applications/Volkov/docs/p6-sort-results.md @@ -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` выполняет эту проверку. diff --git a/applications/Volkov/docs/p7-selection-results.md b/applications/Volkov/docs/p7-selection-results.md new file mode 100644 index 0000000..d77ef51 --- /dev/null +++ b/applications/Volkov/docs/p7-selection-results.md @@ -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 in 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 +``` diff --git a/applications/Volkov/docs/p8-mask-results.md b/applications/Volkov/docs/p8-mask-results.md new file mode 100644 index 0000000..b9f020d --- /dev/null +++ b/applications/Volkov/docs/p8-mask-results.md @@ -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 +``` diff --git a/applications/Volkov/docs/p9-mkdir-results.md b/applications/Volkov/docs/p9-mkdir-results.md new file mode 100644 index 0000000..65f4248 --- /dev/null +++ b/applications/Volkov/docs/p9-mkdir-results.md @@ -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 +``` diff --git a/applications/Volkov/docs/poc-0.1-release.md b/applications/Volkov/docs/poc-0.1-release.md new file mode 100644 index 0000000..d49c9f3 --- /dev/null +++ b/applications/Volkov/docs/poc-0.1-release.md @@ -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. diff --git a/applications/Volkov/docs/test-media.md b/applications/Volkov/docs/test-media.md new file mode 100644 index 0000000..869e575 --- /dev/null +++ b/applications/Volkov/docs/test-media.md @@ -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 получает управляемую ошибку и продолжает +работу. diff --git a/applications/Volkov/docs/ui-reference.md b/applications/Volkov/docs/ui-reference.md new file mode 100644 index 0000000..82c49d4 --- /dev/null +++ b/applications/Volkov/docs/ui-reference.md @@ -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. diff --git a/applications/Volkov/include/sc_app.h b/applications/Volkov/include/sc_app.h new file mode 100644 index 0000000..32ab5df --- /dev/null +++ b/applications/Volkov/include/sc_app.h @@ -0,0 +1,49 @@ +/* sc_app.h — состояние и главный цикл Sprinter Commander. */ + +#ifndef SC_APP_H +#define SC_APP_H + +#include +#include "sc_command.h" +#include "sc_copy.h" +#include "sc_panel.h" + +#define SC_PANEL_COUNT 2u +#define SC_PANEL_VISIBLE 27u +#define SC_EMM_PAGE_COUNT 4u +typedef struct ScApp { + ScPanel panels[SC_PANEL_COUNT]; + uint8_t active_panel; + uint8_t running; + uint8_t emm_block; + uint8_t screen_page; + uint8_t copy_page; + uint16_t emm_total_before; + uint16_t emm_free_before; + ScCopyJob copy_job; + const char *status; +} ScApp; + +int sc_app_init(ScApp *app); +void sc_app_run(ScApp *app); +int sc_app_cleanup(ScApp *app); + +void sc_draw_all(const ScApp *app) __banked; +void sc_draw_panel_rows(const ScApp *app, uint8_t panel_index) __banked; +void sc_draw_panel_row(const ScApp *app, uint8_t panel_index, + uint8_t row) __banked; +void sc_draw_info(const ScApp *app) __banked; +void sc_draw_status(const ScApp *app) __banked; + +uint8_t sc_dialog_confirm_quit(void) __banked; +uint8_t sc_dialog_confirm(const char *title, const char *message) __banked; +void sc_dialog_message(const char *title, const char *message) __banked; +uint8_t sc_dialog_choice(const char *title, const char *message, + const char *keys, const char *buttons) __banked; +uint8_t sc_dialog_input(const char *title, const char *prompt, + char *buffer, uint8_t capacity) __banked; +/* Возвращает выбранную ASCII-клавишу из keys в верхнем регистре; Esc = 0. */ +uint8_t sc_dialog_choice(const char *title, const char *message, + const char *keys, const char *buttons) __banked; + +#endif diff --git a/applications/Volkov/include/sc_command.h b/applications/Volkov/include/sc_command.h new file mode 100644 index 0000000..b8b765f --- /dev/null +++ b/applications/Volkov/include/sc_command.h @@ -0,0 +1,43 @@ +/* sc_command.h — логические команды Commander. */ + +#ifndef SC_COMMAND_H +#define SC_COMMAND_H + +#include "sc_input.h" + +typedef enum { + SC_CMD_NONE = 0, + SC_CMD_CURSOR_UP, + SC_CMD_CURSOR_DOWN, + 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_REFRESH, + SC_CMD_SORT_NAME, + SC_CMD_SORT_EXTENSION, + SC_CMD_SORT_DATE, + SC_CMD_SORT_SIZE, + SC_CMD_SELECT, + SC_CMD_INVERT_SELECTION, + SC_CMD_SELECT_MASK, + SC_CMD_UNSELECT_MASK, + 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_CANCEL +} ScCommand; + +ScCommand sc_command_from_event(const ScKeyEvent *event); + +#endif diff --git a/applications/Volkov/include/sc_copy.h b/applications/Volkov/include/sc_copy.h new file mode 100644 index 0000000..a4b5aef --- /dev/null +++ b/applications/Volkov/include/sc_copy.h @@ -0,0 +1,59 @@ +/* sc_copy.h — состояние безопасного однофайлового copy-job PoC. */ + +#ifndef SC_COPY_H +#define SC_COPY_H + +#include + +#define SC_COPY_PATH_CAPACITY 256u +#define SC_COPY_CHUNK_SIZE 4096u + +#define SC_COPY_IDLE 0u +#define SC_COPY_RUNNING 1u +#define SC_COPY_DONE 2u +#define SC_COPY_CANCELLED 3u +#define SC_COPY_ERROR 4u + +#define SC_COPY_STEP_ERROR (-1) +#define SC_COPY_STEP_DONE 0 +#define SC_COPY_STEP_MORE 1 /* нужен ещё шаг; done==total означает pre-commit */ + +typedef struct { + char source_path[SC_COPY_PATH_CAPACITY]; + char target_path[SC_COPY_PATH_CAPACITY]; + char temp_path[SC_COPY_PATH_CAPACITY]; + uint32_t total; + uint32_t done; + uint16_t source_date; + uint16_t source_time; + int source_fd; + int temp_fd; + int error; + uint8_t page; + uint8_t state; + uint8_t temp_exists; + uint8_t source_attr; + uint8_t existing_attr; + uint8_t preserve_metadata; + uint8_t replace_existing; + uint8_t committed; +} ScCopyJob; + +int sc_copy_begin(ScCopyJob *job, const char *source_dir, + const char *target_dir, const char *name, + uint32_t size, uint8_t page) __banked; +/* source_name и target_name могут различаться; replace включает backup/rollback. */ +int sc_copy_begin_policy(ScCopyJob *job, const char *source_dir, + const char *target_dir, const char *source_name, + const char *target_name, uint32_t size, + uint8_t page, uint8_t replace) __banked; +/* Для tree-job: CWD уже равен source-dir, а target_path подготовлен целиком. */ +int sc_copy_begin_prepared(ScCopyJob *job, const char *name, + uint32_t size, uint8_t page) __banked; +int sc_copy_begin_prepared_policy(ScCopyJob *job, const char *name, + uint32_t size, uint8_t page, + uint8_t replace) __banked; +int sc_copy_step(ScCopyJob *job) __banked; +void sc_copy_cancel(ScCopyJob *job) __banked; + +#endif diff --git a/applications/Volkov/include/sc_copy_ui.h b/applications/Volkov/include/sc_copy_ui.h new file mode 100644 index 0000000..250b00b --- /dev/null +++ b/applications/Volkov/include/sc_copy_ui.h @@ -0,0 +1,10 @@ +/* sc_copy_ui.h — банковый обработчик команды F5 и её прогресса. */ + +#ifndef SC_COPY_UI_H +#define SC_COPY_UI_H + +struct ScApp; + +void sc_copy_selected(struct ScApp *app) __banked; + +#endif diff --git a/applications/Volkov/include/sc_disk.h b/applications/Volkov/include/sc_disk.h new file mode 100644 index 0000000..06d5958 --- /dev/null +++ b/applications/Volkov/include/sc_disk.h @@ -0,0 +1,11 @@ +/* sc_disk.h — проверка свободных кластеров целевого диска через DSKINFO. */ + +#ifndef SC_DISK_H +#define SC_DISK_H + +#include + +/* 1 — места достаточно; 0 — недостаточно; -1 — ошибка DSS. */ +int sc_disk_has_space(const char *path, uint32_t bytes) __banked; + +#endif diff --git a/applications/Volkov/include/sc_entry.h b/applications/Volkov/include/sc_entry.h new file mode 100644 index 0000000..b7a839c --- /dev/null +++ b/applications/Volkov/include/sc_entry.h @@ -0,0 +1,27 @@ +/* sc_entry.h — компактная 24-байтовая запись каталога в EMM. */ + +#ifndef SC_ENTRY_H +#define SC_ENTRY_H + +#include + +#define SC_ENTRY_PARENT 0x01u +#define SC_ENTRY_SELECTED 0x02u +#define SC_ENTRY_DSS_LIMIT 0x04u + +#define SC_DIR_CAPACITY 640u + +typedef struct { + char name[13]; + uint32_t size; + uint16_t date; + uint16_t time; + uint8_t attr; + uint8_t flags; + uint8_t reserved; +} ScDirEntry; + +/* SDCC обязан сохранить зафиксированный дисковый формат без padding. */ +typedef char ScDirEntry_must_be_24_bytes[(sizeof(ScDirEntry) == 24u) ? 1 : -1]; + +#endif diff --git a/applications/Volkov/include/sc_exec.h b/applications/Volkov/include/sc_exec.h new file mode 100644 index 0000000..fc64788 --- /dev/null +++ b/applications/Volkov/include/sc_exec.h @@ -0,0 +1,8 @@ +/* sc_exec.h — низкоуровневый запуск EXE через ESTEX. */ + +#ifndef SC_EXEC_H +#define SC_EXEC_H + +int sc_platform_exec(const char *path); + +#endif diff --git a/applications/Volkov/include/sc_exec_ui.h b/applications/Volkov/include/sc_exec_ui.h new file mode 100644 index 0000000..4ac11ba --- /dev/null +++ b/applications/Volkov/include/sc_exec_ui.h @@ -0,0 +1,10 @@ +/* sc_exec_ui.h — банковый обработчик Enter на EXE. */ + +#ifndef SC_EXEC_UI_H +#define SC_EXEC_UI_H + +struct ScApp; + +void sc_exec_selected(struct ScApp *app) __banked; + +#endif diff --git a/applications/Volkov/include/sc_file_ops.h b/applications/Volkov/include/sc_file_ops.h new file mode 100644 index 0000000..3f45e76 --- /dev/null +++ b/applications/Volkov/include/sc_file_ops.h @@ -0,0 +1,17 @@ +/* sc_file_ops.h — низкоуровневые безопасные файловые операции Commander. */ + +#ifndef SC_FILE_OPS_H +#define SC_FILE_OPS_H + +#include + +/* Переименовать old_name в new_name внутри directory и восстановить CWD. + * 0 — успех; -1 — операция отклонена, CWD восстановлен; + * -2 — восстановить CWD после попытки не удалось. */ +int sc_rename_local(const char *directory, + const char *old_name, + const char *new_name, + char *cwd_buffer, + uint16_t cwd_capacity) __banked; + +#endif diff --git a/applications/Volkov/include/sc_file_ui.h b/applications/Volkov/include/sc_file_ui.h new file mode 100644 index 0000000..e90339f --- /dev/null +++ b/applications/Volkov/include/sc_file_ui.h @@ -0,0 +1,12 @@ +/* sc_file_ui.h — банковые UI-команды безопасных файловых операций. */ + +#ifndef SC_FILE_UI_H +#define SC_FILE_UI_H + +#include "sc_app.h" + +void sc_mkdir_command(ScApp *app) __banked; +void sc_rename_command(ScApp *app) __banked; +void sc_delete_command(ScApp *app) __banked; + +#endif diff --git a/applications/Volkov/include/sc_glyphs.h b/applications/Volkov/include/sc_glyphs.h new file mode 100644 index 0000000..10ebfa5 --- /dev/null +++ b/applications/Volkov/include/sc_glyphs.h @@ -0,0 +1,40 @@ +/* + * sc_glyphs.h — raw-коды CP866/CP437 для интерфейса Commander. + * + * Управляющие байты 0x18..0x1B здесь являются экранными глифами. Их нельзя + * передавать функциям, интерпретирующим CR/LF/BS/TAB. + */ + +#ifndef SC_GLYPHS_H +#define SC_GLYPHS_H + +#define SC_G_H 0xC4u +#define SC_G_V 0xB3u +#define SC_G_TL 0xDAu +#define SC_G_TR 0xBFu +#define SC_G_BL 0xC0u +#define SC_G_BR 0xD9u +#define SC_G_TM 0xC2u +#define SC_G_BM 0xC1u +#define SC_G_ML 0xC3u +#define SC_G_MR 0xB4u +#define SC_G_X 0xC5u + +#define SC_G_ARROW_UP 0x18u +#define SC_G_ARROW_DOWN 0x19u +#define SC_G_ARROW_RIGHT 0x1Au +#define SC_G_ARROW_LEFT 0x1Bu + +#define SC_G_SHADE_LIGHT 0xB0u +#define SC_G_SHADE_MEDIUM 0xB1u +#define SC_G_SHADE_DARK 0xB2u +#define SC_G_BLOCK 0xDBu +#define SC_G_HALF_UP 0xDFu +#define SC_G_HALF_DOWN 0xDCu +#define SC_G_HALF_LEFT 0xDDu +#define SC_G_HALF_RIGHT 0xDEu +#define SC_G_BULLET 0xF9u +#define SC_G_CHECK 0xFBu +#define SC_G_SQUARE 0xFEu + +#endif diff --git a/applications/Volkov/include/sc_input.h b/applications/Volkov/include/sc_input.h new file mode 100644 index 0000000..76d2ea4 --- /dev/null +++ b/applications/Volkov/include/sc_input.h @@ -0,0 +1,21 @@ +/* sc_input.h — нормализованное событие клавиатуры. */ + +#ifndef SC_INPUT_H +#define SC_INPUT_H + +#include + +#define SC_MOD_SHIFT 0x01u +#define SC_MOD_CTRL 0x02u +#define SC_MOD_ALT 0x04u + +typedef struct { + uint8_t ascii; + uint8_t scan; + uint8_t modifiers; +} ScKeyEvent; + +void sc_input_read(ScKeyEvent *event); +uint8_t sc_input_poll(ScKeyEvent *event); + +#endif diff --git a/applications/Volkov/include/sc_metadata.h b/applications/Volkov/include/sc_metadata.h new file mode 100644 index 0000000..a13fddd --- /dev/null +++ b/applications/Volkov/include/sc_metadata.h @@ -0,0 +1,13 @@ +/* sc_metadata.h — узкие wrappers записи файловых метаданных DSS. */ + +#ifndef SC_METADATA_H +#define SC_METADATA_H + +#include + +int sc_metadata_set_datetime(uint8_t fd, uint16_t date, + uint16_t time) __banked; +int sc_metadata_get_attributes(const char *path, uint8_t *attr) __banked; +int sc_metadata_set_attributes(const char *path, uint8_t attr) __banked; + +#endif diff --git a/applications/Volkov/include/sc_panel.h b/applications/Volkov/include/sc_panel.h new file mode 100644 index 0000000..0f4f0e2 --- /dev/null +++ b/applications/Volkov/include/sc_panel.h @@ -0,0 +1,41 @@ +/* sc_panel.h — состояние одной независимой файловой панели. */ + +#ifndef SC_PANEL_H +#define SC_PANEL_H + +#include +#include "sc_entry.h" + +#define SC_SOURCE_REAL_FS 0u + +typedef enum { + SC_SORT_NAME = 0, + SC_SORT_EXTENSION, + SC_SORT_SIZE, + SC_SORT_DATE +} ScSortKey; + +typedef struct { + char path[256]; + uint16_t count; + uint16_t cursor; + uint16_t top; + uint32_t total_size; + uint16_t selected_count; + uint32_t selected_size; + uint8_t source_kind; + uint8_t phys_page; + uint8_t truncated; + uint8_t dirty; + uint8_t sort_key; + uint8_t sort_descending; +} ScPanel; + +int sc_panel_init(ScPanel *panel, uint8_t phys_page); +int sc_panel_refresh(ScPanel *panel); +int sc_panel_selected(const ScPanel *panel, ScDirEntry *entry); +int sc_panel_open_selected(ScPanel *panel); +int sc_panel_parent(ScPanel *panel); +void sc_panel_normalize_view(ScPanel *panel); + +#endif diff --git a/applications/Volkov/include/sc_path.h b/applications/Volkov/include/sc_path.h new file mode 100644 index 0000000..84d108c --- /dev/null +++ b/applications/Volkov/include/sc_path.h @@ -0,0 +1,12 @@ +/* sc_path.h — безопасные операции над путями DOS 8.3 Commander. */ + +#ifndef SC_PATH_H +#define SC_PATH_H + +#include + +int sc_path_join(char *out, uint16_t capacity, + const char *directory, const char *name); +int sc_path_validate_component(const char *name) __banked; + +#endif diff --git a/applications/Volkov/include/sc_screen.h b/applications/Volkov/include/sc_screen.h new file mode 100644 index 0000000..13db63f --- /dev/null +++ b/applications/Volkov/include/sc_screen.h @@ -0,0 +1,38 @@ +/* sc_screen.h — экранная EMM-модель Commander 80x32. */ + +#ifndef SC_SCREEN_API_H +#define SC_SCREEN_API_H + +#include + +#define SC_SCREEN_W 80u +#define SC_SCREEN_H 32u +#define SC_ROW_BYTES 160u +#define SC_SCREEN_BYTES 5120u + +#define SC_SCROLL_UP 1u +#define SC_SCROLL_DOWN 2u + +typedef struct { + uint8_t ch; + uint8_t attr; +} ScCell; + +typedef char ScCell_must_be_2_bytes[(sizeof(ScCell) == 2) ? 1 : -1]; + +void sc_screen_attach(uint8_t page); +void sc_screen_detach(void); +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_fill(uint8_t x, uint8_t y, uint8_t width, + uint8_t ch, uint8_t attr); +void sc_screen_text(uint8_t x, uint8_t y, const char *text, + uint8_t attr, uint8_t max_width); +int sc_screen_present_full(void); +int sc_screen_present_row(uint8_t x, uint8_t y, uint8_t width); +int sc_screen_present_rows(uint8_t first, uint8_t count); +int sc_screen_scroll_rows(uint8_t x, uint8_t y, + uint8_t width, uint8_t height, + uint8_t direction, uint8_t fill_attr); + +#endif diff --git a/applications/Volkov/include/sc_select.h b/applications/Volkov/include/sc_select.h new file mode 100644 index 0000000..b7f6a66 --- /dev/null +++ b/applications/Volkov/include/sc_select.h @@ -0,0 +1,16 @@ +/* sc_select.h — выбор записей текущей панели для групповых операций. */ + +#ifndef SC_SELECT_H +#define SC_SELECT_H + +#include "sc_panel.h" + +#define SC_SELECT_TOGGLE 0u +#define SC_SELECT_INVERT 1u +#define SC_SELECT_MASK_SET 2u +#define SC_SELECT_MASK_CLEAR 3u + +int sc_select_apply(ScPanel *panel, uint8_t action, + const char *mask) __banked; + +#endif diff --git a/applications/Volkov/include/sc_sort.h b/applications/Volkov/include/sc_sort.h new file mode 100644 index 0000000..0a40019 --- /dev/null +++ b/applications/Volkov/include/sc_sort.h @@ -0,0 +1,11 @@ +/* sc_sort.h — сортировка EMM-записей панели. */ + +#ifndef SC_SORT_H +#define SC_SORT_H + +#include "sc_panel.h" + +int sc_sort_panel(ScPanel *panel) __banked; +int sc_sort_set(ScPanel *panel, uint8_t key) __banked; + +#endif diff --git a/applications/Volkov/include/sc_source.h b/applications/Volkov/include/sc_source.h new file mode 100644 index 0000000..33e545a --- /dev/null +++ b/applications/Volkov/include/sc_source.h @@ -0,0 +1,11 @@ +/* sc_source.h — источник настоящего каталога DSS для P3. */ + +#ifndef SC_SOURCE_H +#define SC_SOURCE_H + +#include "sc_panel.h" + +int sc_source_fs_scan(ScPanel *panel) __banked; +int sc_source_fs_change_dir(ScPanel *panel, const char *name) __banked; + +#endif diff --git a/applications/Volkov/include/sc_store.h b/applications/Volkov/include/sc_store.h new file mode 100644 index 0000000..8674144 --- /dev/null +++ b/applications/Volkov/include/sc_store.h @@ -0,0 +1,14 @@ +/* sc_store.h — доступ к записям панели без публикации W3-указателей. */ + +#ifndef SC_STORE_H +#define SC_STORE_H + +#include +#include "sc_panel.h" + +int sc_store_get(const ScPanel *panel, uint16_t index, ScDirEntry *out); +int sc_store_put(const ScPanel *panel, uint16_t index, + const ScDirEntry *entry); +void sc_store_clear(ScPanel *panel); + +#endif diff --git a/applications/Volkov/include/sc_theme.h b/applications/Volkov/include/sc_theme.h new file mode 100644 index 0000000..baaa8d2 --- /dev/null +++ b/applications/Volkov/include/sc_theme.h @@ -0,0 +1,31 @@ +/* + * sc_theme.h — смысловые атрибуты интерфейса. + * + * P2.2 подтвердил системный save/set/restore четырёх текстовых палитровых + * планов. Renderer знает только эти роли, а точные RGB принадлежат модулю + * sc_theme.c. + */ + +#ifndef SC_THEME_H +#define SC_THEME_H + +#include + +#define SC_ATTR_FRAME COLOR(COLOR_WHITE, COLOR_BLUE) +#define SC_ATTR_TITLE COLOR(COLOR_YELLOW, COLOR_BLUE) +#define SC_ATTR_NORMAL COLOR(COLOR_LIGHTCYAN, COLOR_BLUE) +#define SC_ATTR_FILE COLOR(COLOR_LIGHTGRAY, COLOR_BLUE) +#define SC_ATTR_DIR SC_ATTR_FRAME +#define SC_ATTR_EXE SC_ATTR_TITLE +#define SC_ATTR_ACTIVE COLOR(COLOR_BLACK, COLOR_CYAN) +#define SC_ATTR_STATUS COLOR(COLOR_BLACK, COLOR_LIGHTGRAY) +#define SC_ATTR_DIALOG COLOR(COLOR_WHITE, COLOR_RED) +#define SC_ATTR_LIMIT COLOR(COLOR_LIGHTRED, COLOR_BLUE) +#define SC_ATTR_LIMIT_ACTIVE COLOR(COLOR_LIGHTRED, COLOR_CYAN) +#define SC_ATTR_KEYS SC_ATTR_ACTIVE + +int sc_theme_init(void); +void sc_theme_reapply(void); +void sc_theme_shutdown(void); + +#endif diff --git a/applications/Volkov/include/sc_tree.h b/applications/Volkov/include/sc_tree.h new file mode 100644 index 0000000..4adcc6e --- /dev/null +++ b/applications/Volkov/include/sc_tree.h @@ -0,0 +1,43 @@ +/* sc_tree.h — пошаговый BFS-обход дерева для рекурсивных файловых jobs. */ + +#ifndef SC_TREE_H +#define SC_TREE_H + +#include +#include "sc_copy.h" +#include "sc_entry.h" + +#define SC_TREE_STEP_ERROR (-1) +#define SC_TREE_STEP_DONE 0 +#define SC_TREE_STEP_FILE 1 +#define SC_TREE_STEP_DIRECTORY 2 +#define SC_TREE_STEP_SCAN_DIR 3 + +typedef struct { + uint8_t block; + uint8_t page; + uint8_t active; + uint8_t scan_state; + uint16_t head; + uint16_t tail; + uint16_t current; + uint16_t reverse; + uint16_t created_directories; + uint16_t files_seen; + int error; +} ScTreeJob; + +int sc_tree_begin(ScTreeJob *job, ScCopyJob *copy) __banked; +int sc_tree_add_root(ScTreeJob *job, ScCopyJob *copy, + const char *source_base, const char *target_base, + const char *name) __banked; +int sc_tree_add_delete_root(ScTreeJob *job, const char *name) __banked; +int sc_tree_next(ScTreeJob *job, ScCopyJob *copy, + const char *source_base, const char *target_base, + ScDirEntry *entry) __banked; +int sc_tree_delete_next(ScTreeJob *job, ScCopyJob *copy, + const char *source_base, + ScDirEntry *entry) __banked; +int sc_tree_finish(ScTreeJob *job, ScCopyJob *copy) __banked; + +#endif diff --git a/applications/Volkov/include/sc_video.h b/applications/Volkov/include/sc_video.h new file mode 100644 index 0000000..e9de2e8 --- /dev/null +++ b/applications/Volkov/include/sc_video.h @@ -0,0 +1,22 @@ +/* sc_video.h — системный координатный вывод без BIOS window descriptors. */ + +#ifndef SC_VIDEO_H +#define SC_VIDEO_H + +#include + +int sc_video_init(void); +int sc_video_restore(void); +void sc_video_shutdown(void); +int sc_video_present_rect(uint8_t 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, + uint8_t direction); + +void sc_platform_winrest(uint8_t row, uint8_t col, + uint8_t height, uint8_t width, + uint8_t page, uint16_t offset); + +#endif diff --git a/applications/Volkov/src/sc_app.c b/applications/Volkov/src/sc_app.c new file mode 100644 index 0000000..e48e520 --- /dev/null +++ b/applications/Volkov/src/sc_app.c @@ -0,0 +1,405 @@ +/* sc_app.c — инициализация, команды и главный цикл Commander. */ + +#include +#include +#include +#include "sc_app.h" +#include "sc_copy_ui.h" +#include "sc_exec_ui.h" +#include "sc_file_ui.h" +#include "sc_input.h" +#include "sc_panel.h" +#include "sc_screen.h" +#include "sc_select.h" +#include "sc_sort.h" +#include "sc_theme.h" +#include "sc_video.h" + +static void sc_app_redraw(ScApp *app) +{ + sc_draw_all(app); + (void)sc_screen_present_full(); +} + +static void sc_app_redraw_info(ScApp *app) +{ + sc_draw_info(app); + sc_draw_status(app); + (void)sc_screen_present_rows(29u, 2u); +} + +static void sc_app_move_cursor(ScApp *app, ScCommand command) +{ + ScPanel *panel = &app->panels[app->active_panel]; + uint16_t old_cursor = panel->cursor; + uint16_t old_top = panel->top; + uint16_t last; + uint8_t left = app->active_panel ? 41u : 1u; + uint8_t old_row; + uint8_t new_row; + + if (panel->count == 0u) return; + last = (uint16_t)(panel->count - 1u); + + switch (command) { + case SC_CMD_CURSOR_UP: + if (panel->cursor != 0u) panel->cursor--; + break; + case SC_CMD_CURSOR_DOWN: + if (panel->cursor < last) panel->cursor++; + break; + case SC_CMD_PAGE_UP: + panel->cursor = (panel->cursor > SC_PANEL_VISIBLE) ? + (uint16_t)(panel->cursor - SC_PANEL_VISIBLE) : 0u; + break; + case SC_CMD_PAGE_DOWN: + panel->cursor = (uint16_t)(panel->cursor + SC_PANEL_VISIBLE); + if (panel->cursor > last) panel->cursor = last; + break; + case SC_CMD_HOME: + panel->cursor = 0u; + break; + case SC_CMD_END: + panel->cursor = last; + break; + default: + return; + } + + if (panel->cursor < panel->top) panel->top = panel->cursor; + if (panel->cursor >= (uint16_t)(panel->top + SC_PANEL_VISIBLE)) { + panel->top = (uint16_t)(panel->cursor - SC_PANEL_VISIBLE + 1u); + } + if (panel->cursor == old_cursor && panel->top == old_top) return; + + app->status = "Navigation uses only panel state; no directory rescan."; + + if (panel->top == (uint16_t)(old_top + 1u)) { + (void)sc_screen_scroll_rows(left, 1u, 38u, SC_PANEL_VISIBLE, + SC_SCROLL_UP, SC_ATTR_NORMAL); + sc_draw_panel_row(app, app->active_panel, + (uint8_t)(SC_PANEL_VISIBLE - 1u)); + sc_draw_panel_row(app, app->active_panel, + (uint8_t)(old_cursor - panel->top)); + (void)sc_screen_present_row(left, 27u, 38u); + (void)sc_screen_present_row(left, + (uint8_t)(old_cursor - panel->top + 1u), + 38u); + } else if (old_top == (uint16_t)(panel->top + 1u)) { + (void)sc_screen_scroll_rows(left, 1u, 38u, SC_PANEL_VISIBLE, + SC_SCROLL_DOWN, SC_ATTR_NORMAL); + sc_draw_panel_row(app, app->active_panel, 0u); + sc_draw_panel_row(app, app->active_panel, + (uint8_t)(old_cursor - panel->top)); + (void)sc_screen_present_row(left, 1u, 38u); + (void)sc_screen_present_row(left, + (uint8_t)(old_cursor - panel->top + 1u), + 38u); + } else if (panel->top != old_top) { + sc_draw_panel_rows(app, app->active_panel); + sc_app_redraw(app); + return; + } else { + old_row = (uint8_t)(old_cursor - panel->top); + new_row = (uint8_t)(panel->cursor - panel->top); + sc_draw_panel_row(app, app->active_panel, old_row); + sc_draw_panel_row(app, app->active_panel, new_row); + (void)sc_screen_present_row(left, (uint8_t)(old_row + 1u), 38u); + (void)sc_screen_present_row(left, (uint8_t)(new_row + 1u), 38u); + } + sc_app_redraw_info(app); +} + +static void sc_app_stub_dialog(ScApp *app) +{ + sc_dialog_message(" Sprinter Commander ", + "Command is reserved for a later phase."); + app->status = "Reserved command closed; screen restored from EMM model."; + sc_app_redraw(app); +} + +static void sc_app_open(ScApp *app) +{ + ScPanel *panel = &app->panels[app->active_panel]; + int result = sc_panel_open_selected(panel); + + if (result == 0) { + app->status = panel->truncated ? + "Directory opened; not all entries are shown." : + "Directory opened; inactive panel kept its own path."; + sc_app_redraw(app); + } else if (result == 2) { + app->status = "More entries exist, but DSS 1.71 cannot enumerate them."; + sc_app_redraw_info(app); + } else if (result > 0) { + sc_exec_selected(app); + } else { + app->status = "Cannot open directory; DSS path/CWD was restored."; + sc_app_redraw_info(app); + } +} + +static void sc_app_parent(ScApp *app) +{ + ScPanel *panel = &app->panels[app->active_panel]; + if (sc_panel_parent(panel) == 0) { + app->status = panel->truncated ? + "Parent opened; not all entries are shown." : + "Parent directory opened; other panel is unchanged."; + sc_app_redraw(app); + } else { + app->status = "Cannot open parent; DSS path/CWD was restored."; + sc_app_redraw_info(app); + } +} + +static void sc_app_refresh(ScApp *app) +{ + ScPanel *panel = &app->panels[app->active_panel]; + if (sc_panel_refresh(panel) == 0) { + app->status = panel->truncated ? + "Panel refreshed; not all entries are shown." : + "Panel refreshed; cursor restored by entry name."; + sc_app_redraw(app); + } else { + app->status = "Refresh failed; DSS path/CWD was restored."; + sc_app_redraw_info(app); + } +} + +static void sc_app_sort(ScApp *app, uint8_t key) +{ + ScPanel *panel = &app->panels[app->active_panel]; + + if (sc_sort_set(panel, key) != 0) { + app->status = "Cannot sort panel; EMM access failed."; + sc_app_redraw_info(app); + return; + } + + app->status = "Panel sort changed: N/E/D/S and +/- are shown on its frame."; + sc_app_redraw(app); +} + +static void sc_app_select(ScApp *app, uint8_t action) +{ + ScPanel *panel = &app->panels[app->active_panel]; + int result = sc_select_apply(panel, action, 0); + + if (result < 0) { + app->status = "Selection failed: EMM access error."; + } else if (result > 0) { + app->status = "This virtual entry cannot be selected."; + } else { + app->status = "Selection changed; virtual entries are excluded."; + } + sc_app_redraw(app); +} + +static void sc_app_select_mask(ScApp *app, uint8_t action) +{ + ScPanel *panel = &app->panels[app->active_panel]; + char *mask = app->copy_job.source_path; + int result; + + mask[0] = '*'; + mask[1] = '.'; + mask[2] = '*'; + mask[3] = '\0'; + if (!sc_dialog_input(action == SC_SELECT_MASK_SET ? + " Select by mask " : " Unselect by mask ", + "DOS mask (* and ?):", mask, 13u)) { + app->status = "Mask selection cancelled."; + sc_app_redraw(app); + return; + } + + result = sc_select_apply(panel, action, mask); + app->status = result == 0 ? "Mask selection applied." : + "Mask selection failed."; + sc_app_redraw(app); +} + +static void sc_app_dispatch(ScApp *app, ScCommand command) +{ + switch (command) { + case SC_CMD_CURSOR_UP: + case SC_CMD_CURSOR_DOWN: + case SC_CMD_PAGE_UP: + case SC_CMD_PAGE_DOWN: + case SC_CMD_HOME: + case SC_CMD_END: + sc_app_move_cursor(app, command); + break; + + case SC_CMD_PANEL_SWITCH: + app->active_panel ^= 1u; + app->status = "Tab switched the active panel."; + sc_app_redraw(app); + break; + + case SC_CMD_OPEN: + sc_app_open(app); + break; + + case SC_CMD_PARENT: + sc_app_parent(app); + break; + + case SC_CMD_REFRESH: + sc_app_refresh(app); + break; + + case SC_CMD_SORT_NAME: + sc_app_sort(app, SC_SORT_NAME); + break; + + case SC_CMD_SORT_EXTENSION: + sc_app_sort(app, SC_SORT_EXTENSION); + break; + + case SC_CMD_SORT_DATE: + sc_app_sort(app, SC_SORT_DATE); + break; + + case SC_CMD_SORT_SIZE: + sc_app_sort(app, SC_SORT_SIZE); + break; + + case SC_CMD_SELECT: + sc_app_select(app, SC_SELECT_TOGGLE); + break; + + case SC_CMD_INVERT_SELECTION: + sc_app_select(app, SC_SELECT_INVERT); + break; + + case SC_CMD_SELECT_MASK: + sc_app_select_mask(app, SC_SELECT_MASK_SET); + break; + + case SC_CMD_UNSELECT_MASK: + sc_app_select_mask(app, SC_SELECT_MASK_CLEAR); + break; + + case SC_CMD_COPY: + sc_copy_selected(app); + break; + + case SC_CMD_MKDIR: + sc_mkdir_command(app); + break; + + case SC_CMD_MOVE_RENAME: + sc_rename_command(app); + break; + + case SC_CMD_DELETE: + sc_delete_command(app); + break; + + case SC_CMD_HELP: + case SC_CMD_USER_MENU: + case SC_CMD_VIEW: + case SC_CMD_EDIT: + case SC_CMD_MAIN_MENU: + sc_app_stub_dialog(app); + break; + + case SC_CMD_QUIT: + if (sc_dialog_confirm_quit()) { + app->running = 0u; + } else { + app->status = "Exit cancelled; screen restored from EMM model."; + sc_app_redraw(app); + } + break; + + case SC_CMD_CANCEL: + case SC_CMD_NONE: + default: + break; + } +} + +int sc_app_init(ScApp *app) +{ + uint8_t i; + + memset(app, 0, sizeof(*app)); + if (sc_video_init() != 0) return -1; + if (sc_theme_init() != 0) { + sc_video_shutdown(); + return -2; + } + + mem_info(&app->emm_total_before, &app->emm_free_before); + app->emm_block = mem_alloc_pages(SC_EMM_PAGE_COUNT); + if (app->emm_block == 0u) { + sc_theme_shutdown(); + sc_video_shutdown(); + return -3; + } + + for (i = 0u; i < SC_PANEL_COUNT; i++) { + app->panels[i].phys_page = mem_get_page(app->emm_block, i); + } + app->screen_page = mem_get_page(app->emm_block, 2u); + app->copy_page = mem_get_page(app->emm_block, 3u); + if (app->panels[0].phys_page == 0u || + app->panels[1].phys_page == 0u || app->screen_page == 0u || + app->copy_page == 0u) { + mem_free_block(app->emm_block); + app->emm_block = 0u; + sc_theme_shutdown(); + sc_video_shutdown(); + return -4; + } + + for (i = 0u; i < SC_PANEL_COUNT; i++) { + if (sc_panel_init(&app->panels[i], app->panels[i].phys_page) != 0) { + mem_free_block(app->emm_block); + app->emm_block = 0u; + sc_theme_shutdown(); + sc_video_shutdown(); + return -5; + } + } + + app->active_panel = 0u; + app->running = 1u; + app->status = "0.2: independent panel sorting; safe F5 copy and EXEC."; + sc_screen_attach(app->screen_page); + sc_app_redraw(app); + return 0; +} + +void sc_app_run(ScApp *app) +{ + ScKeyEvent event; + + while (app->running) { + sc_input_read(&event); + sc_app_dispatch(app, sc_command_from_event(&event)); + } +} + +int sc_app_cleanup(ScApp *app) +{ + uint16_t total_after = 0u; + uint16_t free_after = 0u; + + if (app->copy_job.state == SC_COPY_RUNNING) { + sc_copy_cancel(&app->copy_job); + } + sc_screen_detach(); + if (app->emm_block != 0u) { + mem_free_block(app->emm_block); + app->emm_block = 0u; + } + mem_info(&total_after, &free_after); + sc_theme_shutdown(); + sc_video_shutdown(); + return (total_after == app->emm_total_before && + free_after == app->emm_free_before) ? 0 : -1; +} diff --git a/applications/Volkov/src/sc_command.c b/applications/Volkov/src/sc_command.c new file mode 100644 index 0000000..efe521f --- /dev/null +++ b/applications/Volkov/src/sc_command.c @@ -0,0 +1,49 @@ +/* sc_command.c — перевод физических клавиш в команды приложения. */ + +#include +#include "sc_command.h" + +ScCommand sc_command_from_event(const ScKeyEvent *event) +{ + if (event->ascii == 0x1Bu) return SC_CMD_CANCEL; + if (event->ascii == 0x09u) return SC_CMD_PANEL_SWITCH; + if (event->ascii == 0x0Du) return SC_CMD_OPEN; + if (event->ascii == 0x08u) return SC_CMD_PARENT; + if (event->ascii == 0x12u) return SC_CMD_REFRESH; + if (event->ascii == '*') return SC_CMD_INVERT_SELECTION; + if (event->ascii == '+') return SC_CMD_SELECT_MASK; + if (event->ascii == '-') return SC_CMD_UNSELECT_MASK; + + if ((event->modifiers & SC_MOD_CTRL) != 0u) { + if (event->scan == KEY_PGUP) return SC_CMD_PARENT; + if (event->scan == 0x13u) return SC_CMD_REFRESH; + if (event->scan == KEY_F3) return SC_CMD_SORT_NAME; + if (event->scan == KEY_F4) return SC_CMD_SORT_EXTENSION; + if (event->scan == KEY_F5) return SC_CMD_SORT_DATE; + if (event->scan == KEY_F6) return SC_CMD_SORT_SIZE; + return SC_CMD_NONE; + } + + if (event->ascii != 0u) return SC_CMD_NONE; + + switch (event->scan) { + case KEY_UP: return SC_CMD_CURSOR_UP; + case KEY_DOWN: return SC_CMD_CURSOR_DOWN; + case KEY_PGUP: return SC_CMD_PAGE_UP; + case KEY_PGDN: return SC_CMD_PAGE_DOWN; + case KEY_HOME: return SC_CMD_HOME; + case KEY_END: return SC_CMD_END; + case KEY_INS: return SC_CMD_SELECT; + case KEY_F1: return SC_CMD_HELP; + case KEY_F2: return SC_CMD_USER_MENU; + case KEY_F3: return SC_CMD_VIEW; + case KEY_F4: return SC_CMD_EDIT; + case KEY_F5: return SC_CMD_COPY; + case KEY_F6: return SC_CMD_MOVE_RENAME; + case KEY_F7: return SC_CMD_MKDIR; + case KEY_F8: return SC_CMD_DELETE; + case KEY_F9: return SC_CMD_MAIN_MENU; + case KEY_F10: return SC_CMD_QUIT; + default: return SC_CMD_NONE; + } +} diff --git a/applications/Volkov/src/sc_copy_job.c b/applications/Volkov/src/sc_copy_job.c new file mode 100644 index 0000000..4d4d527 --- /dev/null +++ b/applications/Volkov/src/sc_copy_job.c @@ -0,0 +1,533 @@ +/* + * sc_copy_job.c — безопасное копирование одного файла для PoC. + * + * Код живёт в W1-банке, данные job — в W2, единственный блок данных — в + * выделенной EMM-странице W3. Target никогда не открывается с усечением: + * сначала создаётся уникальный temp через O_EXCL, а после закрытия обоих fd + * target проверяется повторно и temp атомарно переименовывается. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "sc_copy.h" +#include "sc_disk.h" +#include "sc_metadata.h" +#include "sc_path.h" + +#define SC_COPY_TEMP_ATTEMPTS 256u +#define SC_COPY_BACKUP_ATTEMPTS 256u + +static uint16_t sc_copy_temp_sequence; +static uint16_t sc_copy_backup_sequence; +static char sc_copy_temp_name[13]; +static char sc_copy_backup_name[13]; + +static char sc_hex_digit(uint8_t value) +{ + value &= 0x0Fu; + return (char)(value < 10u ? '0' + value : 'A' + value - 10u); +} + +static void sc_make_temp_name(uint16_t value) +{ + sc_copy_temp_name[0] = '~'; + sc_copy_temp_name[1] = 'S'; + sc_copy_temp_name[2] = 'C'; + sc_copy_temp_name[3] = sc_hex_digit((uint8_t)(value >> 12)); + sc_copy_temp_name[4] = sc_hex_digit((uint8_t)(value >> 8)); + sc_copy_temp_name[5] = sc_hex_digit((uint8_t)(value >> 4)); + sc_copy_temp_name[6] = sc_hex_digit((uint8_t)value); + sc_copy_temp_name[7] = '.'; + sc_copy_temp_name[8] = 'T'; + sc_copy_temp_name[9] = 'M'; + sc_copy_temp_name[10] = 'P'; + sc_copy_temp_name[11] = '\0'; +} + +static void sc_make_backup_name(uint16_t value) +{ + sc_copy_backup_name[0] = '~'; + sc_copy_backup_name[1] = 'S'; + sc_copy_backup_name[2] = 'B'; + sc_copy_backup_name[3] = sc_hex_digit((uint8_t)(value >> 12)); + sc_copy_backup_name[4] = sc_hex_digit((uint8_t)(value >> 8)); + sc_copy_backup_name[5] = sc_hex_digit((uint8_t)(value >> 4)); + sc_copy_backup_name[6] = sc_hex_digit((uint8_t)value); + sc_copy_backup_name[7] = '.'; + sc_copy_backup_name[8] = 'B'; + sc_copy_backup_name[9] = 'A'; + sc_copy_backup_name[10] = 'K'; + sc_copy_backup_name[11] = '\0'; +} + +/* 1 — существует; 0 — достоверно отсутствует; -1 — проверить не удалось. */ +static int sc_path_exists(const char *path) +{ + int fd; + int saved_errno; + + errno = 0; + fd = open(path, O_RDONLY); + if (fd >= 0) { + if (close(fd) != 0) return -1; + return 1; + } + saved_errno = errno; + if (saved_errno == ENOENT || saved_errno == ENOPATH) return 0; + errno = saved_errno; + return -1; +} + +static void sc_copy_close_fd(int *fd) +{ + int owned = *fd; + *fd = -1; + if (owned >= 0) (void)close(owned); +} + +static void sc_copy_remove_temp(ScCopyJob *job) +{ + if (job->temp_exists) { + (void)unlink(job->temp_path); + job->temp_exists = 0u; + } +} + +static int sc_copy_fail(ScCopyJob *job, int error) +{ + if (error == 0) error = EUNKERR; + sc_copy_close_fd(&job->source_fd); + sc_copy_close_fd(&job->temp_fd); + sc_copy_remove_temp(job); + job->error = error; + job->state = SC_COPY_ERROR; + errno = error; + return SC_COPY_STEP_ERROR; +} + +static int sc_copy_create_temp(ScCopyJob *job, const char *target_dir) +{ + uint16_t attempt; + + for (attempt = 0u; attempt < SC_COPY_TEMP_ATTEMPTS; attempt++) { + sc_make_temp_name(sc_copy_temp_sequence++); + if (sc_path_join(job->temp_path, SC_COPY_PATH_CAPACITY, + target_dir, sc_copy_temp_name) != 0) return -1; + errno = 0; + job->temp_fd = open(job->temp_path, + O_WRONLY | O_CREAT | O_EXCL); + if (job->temp_fd >= 0) { + job->temp_exists = 1u; + return 0; + } + if (errno != EEXIST) return -1; + } + errno = EEXIST; + return -1; +} + +static char *sc_copy_path_name(char *path) +{ + char *name = path; + char *scan = path; + + while (*scan != '\0') { + if (*scan == '\\' || *scan == '/') name = scan + 1; + scan++; + } + return name; +} + +/* + * Возвращает байт, который можно временно заменить нулём, чтобы получить + * каталог temp. Для корня режется первый байт имени (`\NAME` -> `\`), + * для обычного пути — предшествующий разделитель (`\DIR\NAME` -> `\DIR`). + */ +static char *sc_copy_directory_cut(char *path, char *name) +{ + char *separator; + + if (name == path) return 0; + separator = name - 1; + if (separator == path || + (separator == path + 2 && path[1] == ':')) return name; + return separator; +} + +/* Выбирает отсутствующее backup-имя, не создавая файла. Полный путь временно + * строится в source_path: source fd к этому моменту уже закрыт. */ +static int sc_copy_choose_backup(ScCopyJob *job, char *target_name) +{ + char *directory_cut = sc_copy_directory_cut(job->target_path, + target_name); + char saved_cut = '\0'; + uint16_t attempt; + int exists; + + if (directory_cut) { + saved_cut = *directory_cut; + *directory_cut = '\0'; + } + for (attempt = 0u; attempt < SC_COPY_BACKUP_ATTEMPTS; attempt++) { + sc_make_backup_name(sc_copy_backup_sequence++); + if (directory_cut) { + if (sc_path_join(job->source_path, SC_COPY_PATH_CAPACITY, + job->target_path, sc_copy_backup_name) != 0) { + *directory_cut = saved_cut; + return -1; + } + } else { + strncpy(job->source_path, sc_copy_backup_name, + SC_COPY_PATH_CAPACITY - 1u); + job->source_path[SC_COPY_PATH_CAPACITY - 1u] = '\0'; + } + exists = sc_path_exists(job->source_path); + if (exists == 0) { + if (directory_cut) *directory_cut = saved_cut; + return 0; + } + if (exists < 0) { + if (directory_cut) *directory_cut = saved_cut; + return -1; + } + } + if (directory_cut) *directory_cut = saved_cut; + errno = EEXIST; + return -1; +} + +static int sc_copy_finish(ScCopyJob *job) +{ + int exists; + int saved_errno; + int rename_errno = 0; + int final_errno = 0; + char *temp_name; + char *target_name; + char *directory_cut; + char saved_cut = '\0'; + uint8_t changed_directory = 0u; + uint8_t replace_target = 0u; + uint8_t backup_exists = 0u; + uint8_t readonly_cleared = 0u; + + if (job->source_fd >= 0) { + int fd = job->source_fd; + job->source_fd = -1; + if (close(fd) != 0) return sc_copy_fail(job, errno); + } + if (job->temp_fd >= 0) { + int fd = job->temp_fd; + if (job->preserve_metadata && + sc_metadata_set_datetime((uint8_t)fd, job->source_date, + job->source_time) != 0) { + return sc_copy_fail(job, errno); + } + job->temp_fd = -1; + if (close(fd) != 0) return sc_copy_fail(job, errno); + } + + exists = sc_path_exists(job->target_path); + if (exists < 0) return sc_copy_fail(job, errno); + if (exists > 0) { + if (!job->replace_existing) return sc_copy_fail(job, EEXIST); + if (sc_metadata_get_attributes(job->target_path, + &job->existing_attr) != 0) { + return sc_copy_fail(job, errno); + } + replace_target = 1u; + } + + /* DSS 1.71 RENAME принимает два имени из текущего каталога. Полный + * путь старого файла он частично разбирает, но полный новый путь затем + * отвергает как EINAME. Поэтому commit выполняется локально в target-dir + * с обязательным восстановлением глобального CWD приложения. */ + temp_name = sc_copy_path_name(job->temp_path); + target_name = sc_copy_path_name(job->target_path); + if (*temp_name == '\0' || *target_name == '\0') { + return sc_copy_fail(job, EINAME); + } + directory_cut = sc_copy_directory_cut(job->temp_path, temp_name); + if (replace_target && sc_copy_choose_backup(job, target_name) != 0) { + return sc_copy_fail(job, errno); + } + if (directory_cut) { + if (!getcwd(job->source_path, SC_COPY_PATH_CAPACITY)) { + return sc_copy_fail(job, errno); + } + saved_cut = *directory_cut; + *directory_cut = '\0'; + if (chdir(job->temp_path) != 0) { + saved_errno = errno; + *directory_cut = saved_cut; + return sc_copy_fail(job, saved_errno); + } + *directory_cut = saved_cut; + changed_directory = 1u; + } + + if (replace_target && (job->existing_attr & FA_RDONLY)) { + if (sc_metadata_set_attributes(target_name, + (uint8_t)(job->existing_attr & (uint8_t)~FA_RDONLY)) != 0) { + rename_errno = errno; + } else { + readonly_cleared = 1u; + } + } + if (replace_target && rename_errno == 0) { + if (rename(target_name, sc_copy_backup_name) != 0) { + rename_errno = errno; + } else { + backup_exists = 1u; + } + } + if (rename_errno == 0 && rename(temp_name, target_name) != 0) { + rename_errno = errno; + } else if (rename_errno == 0) { + job->temp_exists = 0u; + job->committed = 1u; + } + + /* До удаления backup новая запись должна получить все метаданные. При + * отказе ATTRIB возвращаем старый target на место. */ + if (job->committed && job->preserve_metadata && + sc_metadata_set_attributes(target_name, job->source_attr) != 0) { + rename_errno = errno; + } + if (rename_errno != 0 && backup_exists) { + if (job->committed && unlink(target_name) != 0) { + final_errno = errno; + } else if (rename(sc_copy_backup_name, target_name) != 0) { + final_errno = errno; + } else { + backup_exists = 0u; + job->committed = 0u; + if (readonly_cleared && + sc_metadata_set_attributes(target_name, + job->existing_attr) != 0) { + final_errno = errno; + } + } + } + if (rename_errno == 0 && backup_exists) { + if (unlink(sc_copy_backup_name) != 0) final_errno = errno; + else backup_exists = 0u; + } + if (rename_errno != 0 && readonly_cleared && !backup_exists && + !job->committed) { + if (sc_metadata_set_attributes(target_name, + job->existing_attr) != 0 && + final_errno == 0) final_errno = errno; + } + if (changed_directory && chdir(job->source_path) != 0) { + saved_errno = errno; + if (rename_errno != 0) return sc_copy_fail(job, + final_errno != 0 ? + final_errno : rename_errno); + job->error = saved_errno; + job->state = SC_COPY_ERROR; + errno = saved_errno; + return SC_COPY_STEP_ERROR; + } + if (rename_errno != 0) { + return sc_copy_fail(job, final_errno != 0 ? final_errno : rename_errno); + } + if (final_errno != 0) { + job->error = final_errno; + job->state = SC_COPY_ERROR; + errno = final_errno; + return SC_COPY_STEP_ERROR; + } + + job->state = SC_COPY_DONE; + job->error = 0; + return SC_COPY_STEP_DONE; +} + +static int sc_copy_begin_policy_impl(ScCopyJob *job, const char *source_dir, + const char *target_dir, + const char *source_name, + const char *target_name, uint32_t size, + uint8_t page, uint8_t replace) +{ + int exists; + + if (!job || !source_dir || !target_dir || !source_name || !target_name || + source_name[0] == '\0' || target_name[0] == '\0' || page == 0u) { + errno = EINVAL; + return -1; + } + memset(job, 0, sizeof(*job)); + job->source_fd = -1; + job->temp_fd = -1; + job->page = page; + job->total = size; + job->replace_existing = replace ? 1u : 0u; + + if (sc_path_join(job->source_path, SC_COPY_PATH_CAPACITY, + source_dir, source_name) != 0 || + sc_path_join(job->target_path, SC_COPY_PATH_CAPACITY, + target_dir, target_name) != 0) { + return sc_copy_fail(job, errno); + } + + exists = sc_path_exists(job->target_path); + if (exists < 0) return sc_copy_fail(job, errno); + if (exists > 0) { + if (sc_metadata_get_attributes(job->target_path, + &job->existing_attr) != 0) { + return sc_copy_fail(job, errno); + } + if (!job->replace_existing) return sc_copy_fail(job, EEXIST); + } + exists = sc_disk_has_space(job->target_path, size); + if (exists <= 0) return sc_copy_fail(job, exists == 0 ? ENOSPC : errno); + if (sc_copy_create_temp(job, target_dir) != 0) { + return sc_copy_fail(job, errno); + } + job->source_fd = open(job->source_path, O_RDONLY); + if (job->source_fd < 0) return sc_copy_fail(job, errno); + + job->state = SC_COPY_RUNNING; + return 0; +} + +int sc_copy_begin(ScCopyJob *job, const char *source_dir, + const char *target_dir, const char *name, + uint32_t size, uint8_t page) __banked +{ + return sc_copy_begin_policy_impl(job, source_dir, target_dir, name, name, + size, page, 0u); +} + +int sc_copy_begin_policy(ScCopyJob *job, const char *source_dir, + const char *target_dir, const char *source_name, + const char *target_name, uint32_t size, + uint8_t page, uint8_t replace) __banked +{ + return sc_copy_begin_policy_impl(job, source_dir, target_dir, + source_name, target_name, size, page, + replace); +} + +static int sc_copy_begin_prepared_impl(ScCopyJob *job, const char *name, + uint32_t size, uint8_t page, + uint8_t replace) +{ + char *target_name; + char *directory_cut; + char saved_cut; + int exists; + int saved_errno; + + if (!job || !name || name[0] == '\0' || page == 0u || + job->target_path[0] == '\0') { + errno = EINVAL; + return -1; + } + job->source_fd = -1; + job->temp_fd = -1; + job->total = size; + job->done = 0ul; + job->error = 0; + job->page = page; + job->state = SC_COPY_IDLE; + job->temp_exists = 0u; + job->preserve_metadata = 0u; + job->replace_existing = replace ? 1u : 0u; + job->committed = 0u; + job->existing_attr = 0u; + strncpy(job->source_path, name, SC_COPY_PATH_CAPACITY - 1u); + job->source_path[SC_COPY_PATH_CAPACITY - 1u] = '\0'; + + exists = sc_path_exists(job->target_path); + if (exists < 0) return sc_copy_fail(job, errno); + if (exists > 0) { + if (sc_metadata_get_attributes(job->target_path, + &job->existing_attr) != 0) { + return sc_copy_fail(job, errno); + } + if (!job->replace_existing) return sc_copy_fail(job, EEXIST); + } + + exists = sc_disk_has_space(job->target_path, size); + if (exists <= 0) return sc_copy_fail(job, exists == 0 ? ENOSPC : errno); + + target_name = sc_copy_path_name(job->target_path); + directory_cut = sc_copy_directory_cut(job->target_path, target_name); + if (!directory_cut || *target_name == '\0') { + return sc_copy_fail(job, EINAME); + } + saved_cut = *directory_cut; + *directory_cut = '\0'; + if (sc_copy_create_temp(job, job->target_path) != 0) { + saved_errno = errno; + *directory_cut = saved_cut; + return sc_copy_fail(job, saved_errno); + } + *directory_cut = saved_cut; + + job->source_fd = open(job->source_path, O_RDONLY); + if (job->source_fd < 0) return sc_copy_fail(job, errno); + job->state = SC_COPY_RUNNING; + return 0; +} + +int sc_copy_begin_prepared(ScCopyJob *job, const char *name, + uint32_t size, uint8_t page) __banked +{ + return sc_copy_begin_prepared_impl(job, name, size, page, 0u); +} + +int sc_copy_begin_prepared_policy(ScCopyJob *job, const char *name, + uint32_t size, uint8_t page, + uint8_t replace) __banked +{ + return sc_copy_begin_prepared_impl(job, name, size, page, replace); +} + +int sc_copy_step(ScCopyJob *job) __banked +{ + uint32_t remaining; + uint16_t chunk; + int count; + + if (!job || job->state != SC_COPY_RUNNING) { + errno = EINVAL; + return SC_COPY_STEP_ERROR; + } + remaining = job->total - job->done; + if (remaining == 0ul) return sc_copy_finish(job); + chunk = remaining > SC_COPY_CHUNK_SIZE ? + SC_COPY_CHUNK_SIZE : (uint16_t)remaining; + + count = bank_read_page(job->source_fd, job->page, 0u, chunk); + if (count != (int)chunk) { + return sc_copy_fail(job, count < 0 ? errno : EREAD); + } + count = bank_write_page(job->temp_fd, job->page, 0u, chunk); + if (count != (int)chunk) { + return sc_copy_fail(job, count < 0 ? errno : EWRITE); + } + job->done += (uint32_t)chunk; + /* Даже после последнего блока сначала возвращаем управление UI. Так + * между полной записью temp и commit/rename остаётся точка отмены. + * Следующий вызов попадёт в remaining == 0 и выполнит commit. */ + return SC_COPY_STEP_MORE; +} + +void sc_copy_cancel(ScCopyJob *job) __banked +{ + if (!job || job->state != SC_COPY_RUNNING) return; + sc_copy_close_fd(&job->source_fd); + sc_copy_close_fd(&job->temp_fd); + sc_copy_remove_temp(job); + job->error = 0; + job->state = SC_COPY_CANCELLED; +} diff --git a/applications/Volkov/src/sc_copy_ui.c b/applications/Volkov/src/sc_copy_ui.c new file mode 100644 index 0000000..cac913c --- /dev/null +++ b/applications/Volkov/src/sc_copy_ui.c @@ -0,0 +1,556 @@ +/* + * sc_copy_ui.c — банковая оркестрация F5 и отображение прогресса. + * + * Обработчик остаётся синхронным, но после каждого блока возвращает W3, + * обновляет строку состояния и проверяет Esc. Сам блок данных здесь не + * хранится: copy job переносит его через выделенную EMM-страницу ScApp. + */ + +#include +#include +#include +#include "sc_app.h" +#include "sc_copy_ui.h" +#include "sc_input.h" +#include "sc_panel.h" +#include "sc_path.h" +#include "sc_screen.h" +#include "sc_store.h" +#include "sc_tree.h" + +#define SC_COPY_RUN_DONE 0 +#define SC_COPY_RUN_ERROR -1 +#define SC_COPY_RUN_CANCELLED 1 +#define SC_COPY_RUN_SKIPPED 2 + +#define SC_COPY_POLICY_ASK 0u +#define SC_COPY_POLICY_OVERWRITE_ALL 1u +#define SC_COPY_POLICY_SKIP_ALL 2u + +static char sc_copy_status_text[79]; +static char sc_copy_rename_name[13]; + +static char *sc_copy_status_append(char *out, const char *text) +{ + char *limit = sc_copy_status_text + sizeof(sc_copy_status_text) - 1u; + + while (*text != '\0' && out < limit) *out++ = *text++; + *out = '\0'; + return out; +} + +static char *sc_copy_status_u32(char *out, uint32_t value) +{ + char digits[10]; + uint8_t count = 0u; + + do { + digits[count++] = (char)('0' + value % 10ul); + value /= 10ul; + } while (value != 0ul); + while (count != 0u) { + char one[2]; + one[0] = digits[--count]; + one[1] = '\0'; + out = sc_copy_status_append(out, one); + } + return out; +} + +static void sc_copy_redraw_info(ScApp *app) +{ + sc_draw_info(app); + sc_draw_status(app); + (void)sc_screen_present_rows(29u, 2u); +} + +static void sc_copy_redraw_all(ScApp *app) +{ + sc_draw_all(app); + (void)sc_screen_present_full(); +} + +static void sc_copy_progress(ScApp *app, const char *name, + uint16_t number, uint16_t total) +{ + char *out = sc_copy_status_text; + + out = sc_copy_status_append(out, "Copying "); + if (total > 1u) { + out = sc_copy_status_u32(out, number); + out = sc_copy_status_append(out, "/"); + out = sc_copy_status_u32(out, total); + out = sc_copy_status_append(out, " "); + } + out = sc_copy_status_append(out, name); + out = sc_copy_status_append(out, ": "); + out = sc_copy_status_u32(out, app->copy_job.done); + out = sc_copy_status_append(out, "/"); + out = sc_copy_status_u32(out, app->copy_job.total); + (void)sc_copy_status_append(out, " bytes; Esc cancels."); + app->status = sc_copy_status_text; +} + +static void sc_copy_group_status(ScApp *app, const char *prefix, + uint16_t copied, uint16_t total, + uint16_t directories, int error) +{ + char *out = sc_copy_status_text; + + out = sc_copy_status_append(out, prefix); + out = sc_copy_status_u32(out, copied); + out = sc_copy_status_append(out, "/"); + out = sc_copy_status_u32(out, total); + out = sc_copy_status_append(out, " files"); + if (directories != 0u) { + out = sc_copy_status_append(out, "; unprocessed "); + out = sc_copy_status_u32(out, directories); + out = sc_copy_status_append(out, " dirs"); + } + if (error != 0) { + out = sc_copy_status_append(out, "; errno "); + out = sc_copy_status_u32(out, (uint16_t)error); + } + (void)sc_copy_status_append(out, "."); + app->status = sc_copy_status_text; +} + +static void sc_copy_error(ScApp *app, int error) +{ + char *out; + + if (app->copy_job.committed) { + out = sc_copy_status_text; + out = sc_copy_status_append(out, "File committed; finalization failed (errno "); + out = sc_copy_status_u32(out, (uint16_t)error); + (void)sc_copy_status_append(out, ")."); + app->status = sc_copy_status_text; + return; + } + if (error == EEXIST) { + app->status = "Copy refused: target file already exists."; + return; + } + if (error == ENOSPC) { + app->status = "Copy refused: not enough free space; no temp created."; + return; + } + out = sc_copy_status_text; + out = sc_copy_status_append(out, "Copy failed (errno "); + out = sc_copy_status_u32(out, (uint16_t)error); + (void)sc_copy_status_append(out, "); temporary file removed."); + app->status = sc_copy_status_text; +} + +static int sc_copy_run(ScApp *app, const ScDirEntry *entry, + uint16_t number, uint16_t total) +{ + ScKeyEvent event; + int result; + + sc_copy_progress(app, entry->name, number, total); + sc_copy_redraw_info(app); + for (;;) { + if (sc_input_poll(&event) && event.ascii == 0x1Bu) { + sc_copy_cancel(&app->copy_job); + return SC_COPY_RUN_CANCELLED; + } + result = sc_copy_step(&app->copy_job); + if (result == SC_COPY_STEP_MORE) { + sc_copy_progress(app, entry->name, number, total); + sc_copy_redraw_info(app); + continue; + } + if (result == SC_COPY_STEP_ERROR) return SC_COPY_RUN_ERROR; + return SC_COPY_RUN_DONE; + } +} + +static void sc_copy_set_metadata(ScApp *app, const ScDirEntry *entry) +{ + app->copy_job.source_date = entry->date; + app->copy_job.source_time = entry->time; + app->copy_job.source_attr = entry->attr; + app->copy_job.preserve_metadata = 1u; +} + +static uint8_t sc_copy_conflict_action(ScApp *app, uint8_t *policy) +{ + uint8_t action; + + if (*policy == SC_COPY_POLICY_SKIP_ALL) return 'S'; + if (*policy == SC_COPY_POLICY_OVERWRITE_ALL && + !(app->copy_job.existing_attr & FA_RDONLY)) return 'O'; + + action = sc_dialog_choice( + (app->copy_job.existing_attr & FA_RDONLY) ? + " Read-only target " : " Target exists ", + "Choose action for the existing file.", + "OSRAN", "O/S/R A=all N=skip all Esc"); + if (action == 'A') { + *policy = SC_COPY_POLICY_OVERWRITE_ALL; + return 'O'; + } + if (action == 'N') { + *policy = SC_COPY_POLICY_SKIP_ALL; + return 'S'; + } + return action; +} + +static uint8_t sc_copy_input_rename(void) +{ + for (;;) { + sc_copy_rename_name[0] = '\0'; + if (!sc_dialog_input(" Copy under new name ", + "New DOS 8.3 name:", + sc_copy_rename_name, + sizeof(sc_copy_rename_name))) return 0u; + if (sc_path_validate_component(sc_copy_rename_name) == 0) return 1u; + sc_dialog_message(" Invalid target name ", + "Use one DOS 8.3 component."); + } +} + +static int sc_copy_target_set_name(ScCopyJob *job, const char *name) +{ + char *target = job->target_path; + char *write = target; + uint16_t prefix; + uint8_t length = 0u; + + while (*target != '\0') { + if (*target == '\\' || *target == '/') write = target + 1; + target++; + } + prefix = (uint16_t)(write - job->target_path); + while (name[length] != '\0') length++; + if (prefix + length + 1u > SC_COPY_PATH_CAPACITY) { + errno = EINAME; + return -1; + } + while ((*write++ = *name++) != '\0') {} + return 0; +} + +static int sc_copy_one(ScApp *app, const ScPanel *source, + const ScPanel *target, const ScDirEntry *entry, + uint16_t number, uint16_t total, uint8_t *policy) +{ + const char *target_name = entry->name; + uint8_t replace = 0u; + uint8_t action; + + for (;;) { + if (sc_copy_begin_policy(&app->copy_job, source->path, target->path, + entry->name, target_name, entry->size, + app->copy_page, replace) == 0) break; + if (errno != EEXIST || replace) return SC_COPY_RUN_ERROR; + action = sc_copy_conflict_action(app, policy); + if (action == 0u) return SC_COPY_RUN_CANCELLED; + if (action == 'S') return SC_COPY_RUN_SKIPPED; + if (action == 'R') { + if (!sc_copy_input_rename()) return SC_COPY_RUN_CANCELLED; + target_name = sc_copy_rename_name; + replace = 0u; + } else { + replace = 1u; + } + } + sc_copy_set_metadata(app, entry); + return sc_copy_run(app, entry, number, total); +} + +static int sc_copy_tree_file(ScApp *app, const ScDirEntry *entry, + uint16_t number, uint8_t *policy) +{ + uint8_t replace = 0u; + uint8_t action; + + for (;;) { + if (sc_copy_begin_prepared_policy(&app->copy_job, entry->name, + entry->size, app->copy_page, + replace) == 0) break; + if (errno != EEXIST || replace) return SC_COPY_RUN_ERROR; + action = sc_copy_conflict_action(app, policy); + if (action == 0u) return SC_COPY_RUN_CANCELLED; + if (action == 'S') return SC_COPY_RUN_SKIPPED; + if (action == 'R') { + if (!sc_copy_input_rename()) return SC_COPY_RUN_CANCELLED; + if (sc_copy_target_set_name(&app->copy_job, + sc_copy_rename_name) != 0) { + return SC_COPY_RUN_ERROR; + } + replace = 0u; + } else { + replace = 1u; + } + } + sc_copy_set_metadata(app, entry); + return sc_copy_run(app, entry, number, 0u); +} + +static void sc_copy_tree_status(ScApp *app, const char *prefix, + uint16_t files, uint16_t directories, + int error) +{ + char *out = sc_copy_status_text; + + out = sc_copy_status_append(out, prefix); + out = sc_copy_status_u32(out, files); + out = sc_copy_status_append(out, " files; "); + out = sc_copy_status_u32(out, directories); + out = sc_copy_status_append(out, " dirs"); + if (error != 0) { + out = sc_copy_status_append(out, "; errno "); + out = sc_copy_status_u32(out, (uint16_t)error); + } + (void)sc_copy_status_append(out, "."); + app->status = sc_copy_status_text; +} + +static void sc_copy_directory(ScApp *app, ScPanel *source, + ScPanel *target, const ScDirEntry *root, + uint16_t copied_before, uint8_t *policy) +{ + ScTreeJob tree; + ScDirEntry entry; + ScKeyEvent event; + uint16_t copied = copied_before; + uint16_t skipped = 0u; + uint16_t created = 0u; + uint16_t i; + int step = SC_TREE_STEP_DONE; + int result = SC_COPY_RUN_DONE; + int operation_error = 0; + + if (sc_tree_begin(&tree, &app->copy_job) != 0) { + if (copied_before != 0u) { + app->status = sc_panel_refresh(target) != 0 ? + "Files copied, but traversal alloc and refresh failed." : + "Files copied; tree traversal page allocation failed."; + } else { + app->status = "Tree copy failed: cannot allocate traversal page."; + } + sc_copy_redraw_all(app); + return; + } + if (root) { + if (sc_tree_add_root(&tree, &app->copy_job, source->path, + target->path, root->name) != 0) { + operation_error = errno; + step = SC_TREE_STEP_ERROR; + } + } else { + for (i = 0u; i < source->count; i++) { + if (sc_store_get(source, i, &entry) != 0) { + operation_error = ENOMEM; + step = SC_TREE_STEP_ERROR; + break; + } + if (!(entry.flags & SC_ENTRY_SELECTED) || + !(entry.attr & FA_DIREC) || + (entry.flags & (SC_ENTRY_PARENT | SC_ENTRY_DSS_LIMIT))) { + continue; + } + if (sc_tree_add_root(&tree, &app->copy_job, source->path, + target->path, entry.name) != 0) { + operation_error = errno; + step = SC_TREE_STEP_ERROR; + break; + } + } + } + + while (step != SC_TREE_STEP_ERROR) { + if (sc_input_poll(&event) && event.ascii == 0x1Bu) { + result = SC_COPY_RUN_CANCELLED; + break; + } + step = sc_tree_next(&tree, &app->copy_job, source->path, + target->path, &entry); + if (step == SC_TREE_STEP_DONE) break; + if (step == SC_TREE_STEP_ERROR) { + operation_error = tree.error; + break; + } + created = tree.created_directories; + if (step == SC_TREE_STEP_DIRECTORY) { + sc_copy_tree_status(app, "Copying tree: ", copied, created, 0); + sc_copy_redraw_info(app); + continue; + } + result = sc_copy_tree_file(app, &entry, + (uint16_t)(copied + skipped + 1u), policy); + if (result == SC_COPY_RUN_SKIPPED) { + skipped++; + result = SC_COPY_RUN_DONE; + continue; + } + if (result != SC_COPY_RUN_DONE) { + operation_error = app->copy_job.error; + break; + } + copied++; + } + + created = tree.created_directories; + if (sc_tree_finish(&tree, &app->copy_job) != 0 && + operation_error == 0) { + operation_error = errno; + step = SC_TREE_STEP_ERROR; + } + + if ((created != 0u || copied != 0u) && sc_panel_refresh(target) != 0) { + app->status = "Tree copied partly, but passive panel refresh failed."; + } else if (result == SC_COPY_RUN_CANCELLED) { + sc_copy_tree_status(app, "Tree cancelled after ", copied, created, 0); + } else if (step == SC_TREE_STEP_ERROR || result == SC_COPY_RUN_ERROR) { + sc_copy_tree_status(app, "Tree stopped after ", copied, created, + operation_error); + } else { + sc_copy_tree_status(app, "Tree copied: ", copied, created, 0); + } + sc_copy_redraw_all(app); +} + +static int sc_copy_count_group(const ScPanel *source, + uint16_t *files, uint16_t *directories) +{ + ScDirEntry entry; + uint16_t i; + + *files = 0u; + *directories = 0u; + for (i = 0u; i < source->count; i++) { + if (sc_store_get(source, i, &entry) != 0) return -1; + if (!(entry.flags & SC_ENTRY_SELECTED)) continue; + if (entry.attr & FA_DIREC) (*directories)++; + else if (!(entry.flags & SC_ENTRY_DSS_LIMIT)) (*files)++; + } + return 0; +} + +static void sc_copy_group(ScApp *app, ScPanel *source, ScPanel *target, + uint8_t *policy) +{ + ScDirEntry entry; + uint16_t files; + uint16_t directories; + uint16_t copied = 0u; + uint16_t skipped = 0u; + uint16_t i; + int result = SC_COPY_RUN_DONE; + int operation_error = 0; + + if (sc_copy_count_group(source, &files, &directories) != 0) { + app->status = "Group copy failed: EMM access error."; + sc_copy_redraw_info(app); + return; + } + if (files == 0u && directories == 0u) { + app->status = "Copy refused: selected group has no real entries."; + sc_copy_redraw_info(app); + return; + } + + for (i = 0u; i < source->count; i++) { + if (sc_store_get(source, i, &entry) != 0) { + result = SC_COPY_RUN_ERROR; + operation_error = ENOMEM; + break; + } + if (!(entry.flags & SC_ENTRY_SELECTED) || + (entry.attr & FA_DIREC) || + (entry.flags & SC_ENTRY_DSS_LIMIT)) continue; + result = sc_copy_one(app, source, target, &entry, + (uint16_t)(copied + skipped + 1u), files, + policy); + if (result == SC_COPY_RUN_SKIPPED) { + skipped++; + result = SC_COPY_RUN_DONE; + continue; + } + if (result != SC_COPY_RUN_DONE) { + operation_error = app->copy_job.error; + break; + } + copied++; + } + + if (result == SC_COPY_RUN_DONE && directories != 0u) { + sc_copy_directory(app, source, target, 0, copied, policy); + return; + } + + if (copied != 0u && sc_panel_refresh(target) != 0) { + app->status = "Files copied, but passive panel refresh failed."; + } else if (result == SC_COPY_RUN_CANCELLED) { + sc_copy_group_status(app, "Group cancelled after ", copied, files, + directories, 0); + } else if (result == SC_COPY_RUN_ERROR) { + sc_copy_group_status(app, "Group stopped after ", copied, files, + directories, operation_error); + } else { + sc_copy_group_status(app, "Copied ", copied, files, + directories, 0); + } + sc_copy_redraw_all(app); +} + +void sc_copy_selected(ScApp *app) __banked +{ + ScPanel *source = &app->panels[app->active_panel]; + ScPanel *target = &app->panels[app->active_panel ^ 1u]; + ScDirEntry entry; + int result; + uint8_t policy = SC_COPY_POLICY_ASK; + + if (source->selected_count != 0u) { + sc_copy_group(app, source, target, &policy); + return; + } + + if (sc_panel_selected(source, &entry) != 0) { + app->status = "Copy refused: active panel has no selected entry."; + sc_copy_redraw_info(app); + return; + } + if ((entry.flags & SC_ENTRY_DSS_LIMIT) != 0u) { + app->status = "Copy refused: the DSS limit marker is not a file."; + sc_copy_redraw_info(app); + return; + } + if ((entry.attr & FA_DIREC) != 0u) { + sc_copy_directory(app, source, target, &entry, 0u, &policy); + return; + } + + result = sc_copy_one(app, source, target, &entry, 1u, 1u, &policy); + if (result == SC_COPY_RUN_ERROR) { + sc_copy_error(app, app->copy_job.error); + sc_copy_redraw_all(app); + return; + } + if (result == SC_COPY_RUN_CANCELLED) { + app->status = "Copy cancelled; temporary file removed."; + sc_copy_redraw_all(app); + return; + } + if (result == SC_COPY_RUN_SKIPPED) { + app->status = "Copy skipped; existing target was not changed."; + sc_copy_redraw_all(app); + return; + } + + if (sc_panel_refresh(target) != 0) { + app->status = "File copied, but passive panel refresh failed."; + } else { + char *out = sc_copy_status_text; + out = sc_copy_status_append(out, "Copied "); + out = sc_copy_status_append(out, entry.name); + (void)sc_copy_status_append(out, " to the passive panel."); + app->status = sc_copy_status_text; + } + sc_copy_redraw_all(app); +} diff --git a/applications/Volkov/src/sc_dialog.c b/applications/Volkov/src/sc_dialog.c new file mode 100644 index 0000000..6441689 --- /dev/null +++ b/applications/Volkov/src/sc_dialog.c @@ -0,0 +1,169 @@ +/* sc_dialog.c — единая модальная поверхность сообщений Commander. */ + +#include +#include +#include "sc_app.h" +#include "sc_glyphs.h" +#include "sc_input.h" +#include "sc_screen.h" +#include "sc_theme.h" + +#define DIALOG_X 18u +#define DIALOG_Y 11u +#define DIALOG_W 44u +#define DIALOG_H 8u + +static void sc_dialog_box(const char *title, const char *message, + const char *buttons) +{ + uint8_t x; + uint8_t y; + uint8_t right = (uint8_t)(DIALOG_X + DIALOG_W - 1u); + uint8_t bottom = (uint8_t)(DIALOG_Y + DIALOG_H - 1u); + + for (y = DIALOG_Y; y <= bottom; y++) { + sc_screen_fill(DIALOG_X, y, DIALOG_W, ' ', SC_ATTR_DIALOG); + } + sc_screen_cell(DIALOG_X, DIALOG_Y, SC_G_TL, SC_ATTR_DIALOG); + sc_screen_cell(right, DIALOG_Y, SC_G_TR, SC_ATTR_DIALOG); + sc_screen_cell(DIALOG_X, bottom, SC_G_BL, SC_ATTR_DIALOG); + sc_screen_cell(right, bottom, SC_G_BR, SC_ATTR_DIALOG); + for (x = (uint8_t)(DIALOG_X + 1u); x < right; x++) { + sc_screen_cell(x, DIALOG_Y, SC_G_H, SC_ATTR_DIALOG); + sc_screen_cell(x, bottom, SC_G_H, SC_ATTR_DIALOG); + } + for (y = (uint8_t)(DIALOG_Y + 1u); y < bottom; y++) { + sc_screen_cell(DIALOG_X, y, SC_G_V, SC_ATTR_DIALOG); + sc_screen_cell(right, y, SC_G_V, SC_ATTR_DIALOG); + } + sc_screen_text((uint8_t)(DIALOG_X + 3u), DIALOG_Y, title, + SC_ATTR_DIALOG, 34u); + sc_screen_text((uint8_t)(DIALOG_X + 3u), (uint8_t)(DIALOG_Y + 3u), + message, SC_ATTR_DIALOG, 38u); + sc_screen_text((uint8_t)(DIALOG_X + 3u), (uint8_t)(DIALOG_Y + 5u), + buttons, SC_ATTR_DIALOG, 38u); + (void)sc_screen_present_full(); +} + +uint8_t sc_dialog_confirm(const char *title, const char *message) __banked +{ + ScKeyEvent event; + + sc_dialog_box(title, message, "Y/Enter = yes N/Esc = no"); + for (;;) { + sc_input_read(&event); + if (event.ascii == 'y' || event.ascii == 'Y' || + event.ascii == 0x0Du) return 1u; + if (event.ascii == 'n' || event.ascii == 'N' || + event.ascii == 0x1Bu) return 0u; + } +} + +uint8_t sc_dialog_confirm_quit(void) __banked +{ + return sc_dialog_confirm(" Exit Commander ", + "Leave Sprinter Commander?"); +} + +void sc_dialog_message(const char *title, const char *message) __banked +{ + ScKeyEvent event; + + sc_dialog_box(title, message, "Enter/Esc = close"); + for (;;) { + sc_input_read(&event); + if (event.ascii == 0x0Du || event.ascii == 0x1Bu) return; + } +} + +uint8_t sc_dialog_choice(const char *title, const char *message, + const char *keys, const char *buttons) __banked +{ + ScKeyEvent event; + const char *scan; + uint8_t key; + + sc_dialog_box(title, message, buttons); + for (;;) { + sc_input_read(&event); + if (event.ascii == 0x1Bu) return 0u; + key = event.ascii; + if (key >= 'a' && key <= 'z') key -= (uint8_t)('a' - 'A'); + scan = keys; + while (*scan != '\0') { + if ((uint8_t)*scan++ == key) return key; + } + } +} + +static void sc_dialog_input_row(const char *buffer, uint8_t cursor) +{ + uint8_t length = 0u; + + while (buffer[length] != '\0' && length < 38u) length++; + sc_screen_fill((uint8_t)(DIALOG_X + 3u), + (uint8_t)(DIALOG_Y + 4u), 38u, ' ', SC_ATTR_ACTIVE); + sc_screen_text((uint8_t)(DIALOG_X + 3u), + (uint8_t)(DIALOG_Y + 4u), buffer, + SC_ATTR_ACTIVE, 38u); + sc_screen_cell((uint8_t)(DIALOG_X + 3u + cursor), + (uint8_t)(DIALOG_Y + 4u), + cursor < length ? (uint8_t)buffer[cursor] : ' ', + SC_ATTR_DIALOG); + (void)sc_screen_present_row((uint8_t)(DIALOG_X + 3u), + (uint8_t)(DIALOG_Y + 4u), 38u); +} + +uint8_t sc_dialog_input(const char *title, const char *prompt, + char *buffer, uint8_t capacity) __banked +{ + ScKeyEvent event; + uint8_t length = 0u; + uint8_t cursor; + uint8_t limit; + uint8_t i; + + if (!buffer || capacity < 2u) return 0u; + limit = (uint8_t)(capacity - 1u); + if (limit > 37u) limit = 37u; + while (buffer[length] != '\0' && length < limit) length++; + buffer[length] = '\0'; + cursor = length; + + sc_dialog_box(title, prompt, "Enter = apply Esc = cancel"); + sc_dialog_input_row(buffer, cursor); + for (;;) { + sc_input_read(&event); + if (event.ascii == 0x0Du) return 1u; + if (event.ascii == 0x1Bu) return 0u; + + if (event.ascii == 0x08u) { + if (cursor != 0u) { + cursor--; + for (i = cursor; i < length; i++) buffer[i] = buffer[i + 1u]; + length--; + } + } else if (event.ascii >= 0x20u && event.ascii != 0x7Fu) { + if (length < limit) { + i = length; + while (i > cursor) { + buffer[i] = buffer[i - 1u]; + i--; + } + buffer[cursor++] = (char)event.ascii; + length++; + buffer[length] = '\0'; + } + } else if (event.ascii == 0u) { + if (event.scan == KEY_LEFT && cursor != 0u) cursor--; + else if (event.scan == KEY_RIGHT && cursor < length) cursor++; + else if (event.scan == KEY_HOME) cursor = 0u; + else if (event.scan == KEY_END) cursor = length; + else if (event.scan == KEY_DEL && cursor < length) { + for (i = cursor; i < length; i++) buffer[i] = buffer[i + 1u]; + length--; + } + } + sc_dialog_input_row(buffer, cursor); + } +} diff --git a/applications/Volkov/src/sc_disk.c b/applications/Volkov/src/sc_disk.c new file mode 100644 index 0000000..c9b9eb0 --- /dev/null +++ b/applications/Volkov/src/sc_disk.c @@ -0,0 +1,82 @@ +/* + * sc_disk.c — preflight свободного места через ESTEX DSKINFO ($03). + * + * DSS 1.71 возвращает free clusters как DE':DE, bytes/sector в BC и + * sectors/cluster в A. Raw-leaf сохраняет обе половины shadow-регистров; + * C-обвязка сравнивает кластеры без округления размера вниз. + */ + +#include +#include +#include +#include "sc_disk.h" + +typedef struct { + uint32_t free_clusters; + uint16_t bytes_per_sector; + uint8_t sectors_per_cluster; +} ScDiskInfo; + +static ScDiskInfo sc_disk_raw_info; + +static int sc_disk_info_raw(uint8_t disk) __naked +{ + (void)disk; + __asm + ;; __sdcccall(1): disk в A. + push ix + ld b, #1 ; DSS 1.71: пересчитать free clusters + ld c, #0x03 ; ESTEX DSKINFO + rst #0x10 + jr c, _sc_disk_info_error + ld (_sc_disk_raw_info + 0), de + exx + ld (_sc_disk_raw_info + 2), de + exx + ld (_sc_disk_raw_info + 4), bc + ld (_sc_disk_raw_info + 6), a + pop ix + ld de, #0 + ret + _sc_disk_info_error: + pop ix + call __errno_set + ld de, #-1 + ret + __endasm; +} + +int sc_disk_has_space(const char *path, uint32_t bytes) __banked +{ + uint32_t cluster_bytes; + uint32_t required_clusters; + uint8_t disk; + uint8_t first; + + if (!path) { + errno = EINVAL; + return -1; + } + if (path[0] != '\0' && path[1] == ':') { + first = (uint8_t)path[0]; + if (first >= 'a' && first <= 'z') first -= (uint8_t)('a' - 'A'); + if (first < 'A' || first > 'Z') { + errno = ENODRV; + return -1; + } + disk = (uint8_t)(first - 'A'); + } else { + disk = getdisk(); + } + if (sc_disk_info_raw(disk) != 0) return -1; + if (bytes == 0ul) return 1; + cluster_bytes = (uint32_t)sc_disk_raw_info.bytes_per_sector * + (uint32_t)sc_disk_raw_info.sectors_per_cluster; + if (cluster_bytes == 0ul) { + errno = EMEDIA; + return -1; + } + required_clusters = bytes / cluster_bytes; + if (bytes % cluster_bytes != 0ul) required_clusters++; + return sc_disk_raw_info.free_clusters >= required_clusters ? 1 : 0; +} diff --git a/applications/Volkov/src/sc_draw.c b/applications/Volkov/src/sc_draw.c new file mode 100644 index 0000000..5db628c --- /dev/null +++ b/applications/Volkov/src/sc_draw.c @@ -0,0 +1,358 @@ +/* sc_draw.c — renderer двух независимых файловых панелей, не знающий ESTEX. */ + +#include +#include +#include "sc_app.h" +#include "sc_glyphs.h" +#include "sc_screen.h" +#include "sc_store.h" +#include "sc_theme.h" + +#define PANEL_W 40u +#define PANEL_INNER_W 38u +#define PANEL_TOP 0u +#define PANEL_FIRST 1u +#define PANEL_BOTTOM 28u + +/* Стандартный профиль 1.0: отдельный цвет только для точного `.EXE`. */ +static uint8_t sc_draw_is_exe(const char *name) +{ + const char *extension = 0; + + while (*name != '\0') { + if (*name == '.') extension = name + 1; + name++; + } + if (extension == 0) return 0u; + return (uint8_t)((extension[0] == 'E' || extension[0] == 'e') && + (extension[1] == 'X' || extension[1] == 'x') && + (extension[2] == 'E' || extension[2] == 'e') && + extension[3] == '\0'); +} + +static void sc_draw_dec3(uint8_t x, uint8_t y, uint16_t value, + uint8_t attr) +{ + sc_screen_cell(x, y, (uint8_t)('0' + (value / 100u) % 10u), attr); + sc_screen_cell((uint8_t)(x + 1u), y, + (uint8_t)('0' + (value / 10u) % 10u), attr); + sc_screen_cell((uint8_t)(x + 2u), y, + (uint8_t)('0' + value % 10u), attr); +} + +static void sc_draw_dec5(uint8_t x, uint8_t y, uint16_t value, + uint8_t attr) +{ + uint16_t divisor = 10000u; + uint8_t i; + for (i = 0u; i < 5u; i++) { + sc_screen_cell((uint8_t)(x + i), y, + (uint8_t)('0' + (value / divisor) % 10u), attr); + divisor /= 10u; + } +} + +static void sc_draw_dec2(uint8_t x, uint8_t y, uint8_t value, + uint8_t attr) +{ + sc_screen_cell(x, y, (uint8_t)('0' + (value / 10u) % 10u), attr); + sc_screen_cell((uint8_t)(x + 1u), y, + (uint8_t)('0' + value % 10u), attr); +} + +static void sc_draw_dec4(uint8_t x, uint8_t y, uint16_t value, + uint8_t attr) +{ + uint16_t divisor = 1000u; + uint8_t i; + for (i = 0u; i < 4u; i++) { + sc_screen_cell((uint8_t)(x + i), y, + (uint8_t)('0' + (value / divisor) % 10u), attr); + divisor /= 10u; + } +} + +static void sc_draw_u32_right(uint8_t x, uint8_t y, uint32_t value, + uint8_t width, uint8_t attr) +{ + uint8_t i; + for (i = 0u; i < width; i++) { + uint8_t pos = (uint8_t)(width - i - 1u); + uint8_t ch = (uint8_t)('0' + (uint8_t)(value % 10ul)); + sc_screen_cell((uint8_t)(x + pos), y, ch, attr); + value /= 10ul; + if (value == 0ul) { + while (pos != 0u) { + pos--; + sc_screen_cell((uint8_t)(x + pos), y, ' ', attr); + } + break; + } + } +} + +static void sc_draw_path_tail(uint8_t x, uint8_t y, const char *path, + uint8_t width, uint8_t attr) +{ + uint16_t length = 0u; + const char *shown = path; + + while (path[length] != '\0') length++; + if (length > width) { + shown = &path[length - width + 1u]; + sc_screen_cell(x, y, '<', attr); + sc_screen_text((uint8_t)(x + 1u), y, shown, attr, + (uint8_t)(width - 1u)); + } else { + sc_screen_text(x, y, shown, attr, width); + } +} + +static void sc_draw_date(uint8_t x, uint8_t y, uint16_t date, uint8_t attr) +{ + uint16_t year = (uint16_t)(1980u + (date >> 9)); + uint8_t month = (uint8_t)((date >> 5) & 0x0Fu); + uint8_t day = (uint8_t)(date & 0x1Fu); + + if (date == 0u) { + sc_screen_text(x, y, "----------", attr, 10u); + return; + } + sc_draw_dec4(x, y, year, attr); + sc_screen_cell((uint8_t)(x + 4u), y, '-', attr); + sc_draw_dec2((uint8_t)(x + 5u), y, month, attr); + sc_screen_cell((uint8_t)(x + 7u), y, '-', attr); + sc_draw_dec2((uint8_t)(x + 8u), y, day, attr); +} + +static void sc_draw_time(uint8_t x, uint8_t y, uint16_t time, uint8_t attr) +{ + sc_draw_dec2(x, y, (uint8_t)(time >> 11), attr); + sc_screen_cell((uint8_t)(x + 2u), y, ':', attr); + sc_draw_dec2((uint8_t)(x + 3u), y, + (uint8_t)((time >> 5) & 0x3Fu), attr); +} + +static void sc_draw_frame(uint8_t left) +{ + uint8_t y; + uint8_t right = (uint8_t)(left + PANEL_W - 1u); + + sc_screen_cell(left, PANEL_TOP, SC_G_TL, SC_ATTR_FRAME); + sc_screen_fill((uint8_t)(left + 1u), PANEL_TOP, PANEL_INNER_W, + SC_G_H, SC_ATTR_FRAME); + sc_screen_cell(right, PANEL_TOP, SC_G_TR, SC_ATTR_FRAME); + for (y = PANEL_FIRST; y < PANEL_BOTTOM; y++) { + sc_screen_cell(left, y, SC_G_V, SC_ATTR_FRAME); + sc_screen_cell(right, y, SC_G_V, SC_ATTR_FRAME); + } + sc_screen_cell(left, PANEL_BOTTOM, SC_G_BL, SC_ATTR_FRAME); + sc_screen_fill((uint8_t)(left + 1u), PANEL_BOTTOM, PANEL_INNER_W, + SC_G_H, SC_ATTR_FRAME); + sc_screen_cell(right, PANEL_BOTTOM, SC_G_BR, SC_ATTR_FRAME); +} + +static void sc_draw_title(const ScApp *app, uint8_t panel_index) +{ + uint8_t left = panel_index ? 40u : 0u; + const ScPanel *panel = &app->panels[panel_index]; + + sc_screen_cell((uint8_t)(left + 2u), 0u, + panel_index ? 'R' : 'L', SC_ATTR_TITLE); + sc_draw_path_tail((uint8_t)(left + 4u), 0u, panel->path, + 23u, SC_ATTR_TITLE); + if (app->active_panel == panel_index) { + sc_screen_text((uint8_t)(left + 29u), 0u, "[ACTIVE]", + SC_ATTR_ACTIVE, 8u); + } else { + sc_screen_text((uint8_t)(left + 29u), 0u, "passive ", + SC_ATTR_NORMAL, 9u); + } +} + +static void sc_draw_sort_mode(uint8_t left, const ScPanel *panel) +{ + uint8_t key; + + switch (panel->sort_key) { + case SC_SORT_EXTENSION: key = 'E'; break; + case SC_SORT_SIZE: key = 'S'; break; + case SC_SORT_DATE: key = 'D'; break; + default: key = 'N'; break; + } + sc_screen_cell((uint8_t)(left + 3u), PANEL_BOTTOM, key, SC_ATTR_TITLE); + sc_screen_cell((uint8_t)(left + 4u), PANEL_BOTTOM, + panel->sort_descending ? '-' : '+', SC_ATTR_TITLE); +} + +static void sc_draw_panel_footer(uint8_t left, const ScPanel *panel) +{ + if (panel->selected_count != 0u) { + /* Footer выбора — цельная строка, а не подписи поверх линии рамки. */ + sc_screen_fill((uint8_t)(left + 2u), PANEL_BOTTOM, + 36u, ' ', SC_ATTR_TITLE); + } + sc_draw_sort_mode(left, panel); + if (panel->selected_count != 0u) { + sc_draw_u32_right((uint8_t)(left + 6u), PANEL_BOTTOM, + panel->selected_size, 10u, SC_ATTR_TITLE); + sc_screen_text((uint8_t)(left + 17u), PANEL_BOTTOM, + "bytes in", SC_ATTR_TITLE, 8u); + sc_draw_dec3((uint8_t)(left + 26u), PANEL_BOTTOM, + panel->selected_count, SC_ATTR_TITLE); + sc_screen_text((uint8_t)(left + 30u), PANEL_BOTTOM, + "selected", SC_ATTR_TITLE, 8u); + } else { + sc_draw_dec3((uint8_t)(left + 14u), PANEL_BOTTOM, + panel->count, SC_ATTR_TITLE); + sc_screen_text((uint8_t)(left + 18u), PANEL_BOTTOM, + panel->truncated ? "entries+" : "entries", + SC_ATTR_TITLE, 8u); + } +} + +void sc_draw_panel_row(const ScApp *app, uint8_t panel_index, + uint8_t row) __banked +{ + const ScPanel *panel = &app->panels[panel_index]; + uint8_t left = panel_index ? 41u : 1u; + uint16_t item = (uint16_t)(panel->top + row); + uint8_t y = (uint8_t)(PANEL_FIRST + row); + uint8_t attr; + ScDirEntry entry; + + if (row >= SC_PANEL_VISIBLE) return; + sc_screen_fill(left, y, PANEL_INNER_W, ' ', SC_ATTR_NORMAL); + if (item >= panel->count) return; + + if (sc_store_get(panel, item, &entry) != 0) { + sc_screen_text((uint8_t)(left + 2u), y, "", + SC_ATTR_DIALOG, 13u); + return; + } + + if (entry.flags & SC_ENTRY_DSS_LIMIT) { + attr = (app->active_panel == panel_index && item == panel->cursor) ? + SC_ATTR_LIMIT_ACTIVE : SC_ATTR_LIMIT; + } else if (app->active_panel == panel_index && item == panel->cursor) { + attr = SC_ATTR_ACTIVE; + } else if (entry.attr & FA_DIREC) { + attr = SC_ATTR_DIR; + } else if (sc_draw_is_exe(entry.name)) { + attr = SC_ATTR_EXE; + } else { + attr = SC_ATTR_FILE; + } + sc_screen_fill(left, y, PANEL_INNER_W, ' ', attr); + + sc_screen_cell(left, y, + (app->active_panel == panel_index && + item == panel->cursor) ? '>' : ' ', + attr); + sc_screen_cell((uint8_t)(left + 1u), y, + (entry.flags & SC_ENTRY_SELECTED) ? '*' : ' ', attr); + sc_screen_text((uint8_t)(left + 2u), y, entry.name, attr, 13u); + if (entry.flags & SC_ENTRY_DSS_LIMIT) { + sc_screen_text((uint8_t)(left + 17u), y, "", attr, 11u); + return; + } + if (entry.attr & FA_DIREC) { + sc_screen_text((uint8_t)(left + 17u), y, "", attr, 5u); + } else { + sc_draw_u32_right((uint8_t)(left + 17u), y, entry.size, 10u, attr); + } + sc_screen_cell((uint8_t)(left + 29u), y, + (entry.attr & FA_RDONLY) ? 'R' : '-', attr); + sc_screen_cell((uint8_t)(left + 30u), y, + (entry.attr & FA_HIDDEN) ? 'H' : '-', attr); + sc_screen_cell((uint8_t)(left + 31u), y, + (entry.attr & FA_SYSTEM) ? 'S' : '-', attr); + sc_screen_cell((uint8_t)(left + 32u), y, + (entry.attr & FA_ARCH) ? 'A' : '-', attr); +} + +void sc_draw_panel_rows(const ScApp *app, uint8_t panel_index) __banked +{ + uint8_t row; + for (row = 0u; row < SC_PANEL_VISIBLE; row++) { + sc_draw_panel_row(app, panel_index, row); + } +} + +void sc_draw_info(const ScApp *app) __banked +{ + const ScPanel *panel = &app->panels[app->active_panel]; + ScDirEntry entry; + + sc_screen_fill(0u, 29u, SC_SCREEN_W, ' ', SC_ATTR_STATUS); + sc_screen_cell(1u, 29u, app->active_panel ? 'R' : 'L', SC_ATTR_STATUS); + sc_draw_dec5(3u, 29u, panel->cursor, SC_ATTR_STATUS); + sc_screen_cell(8u, 29u, '/', SC_ATTR_STATUS); + sc_draw_dec5(9u, 29u, panel->count, SC_ATTR_STATUS); + if (sc_panel_selected(panel, &entry) == 0) { + sc_screen_text(15u, 29u, entry.name, SC_ATTR_STATUS, 13u); + if (entry.flags & SC_ENTRY_DSS_LIMIT) { + sc_screen_text(29u, 29u, "", SC_ATTR_STATUS, 11u); + } else if (entry.attr & FA_DIREC) { + sc_screen_text(29u, 29u, " ", SC_ATTR_STATUS, 10u); + } else { + sc_draw_u32_right(29u, 29u, entry.size, 10u, SC_ATTR_STATUS); + } + if (!(entry.flags & SC_ENTRY_DSS_LIMIT)) { + sc_draw_date(40u, 29u, entry.date, SC_ATTR_STATUS); + sc_draw_time(51u, 29u, entry.time, SC_ATTR_STATUS); + sc_screen_cell(57u, 29u, + (entry.attr & FA_RDONLY) ? 'R' : '-', SC_ATTR_STATUS); + sc_screen_cell(58u, 29u, + (entry.attr & FA_HIDDEN) ? 'H' : '-', SC_ATTR_STATUS); + sc_screen_cell(59u, 29u, + (entry.attr & FA_SYSTEM) ? 'S' : '-', SC_ATTR_STATUS); + sc_screen_cell(60u, 29u, + (entry.attr & FA_ARCH) ? 'A' : '-', SC_ATTR_STATUS); + } + } + sc_screen_text(61u, 29u, + panel->selected_count ? "sel" : "sum", + SC_ATTR_STATUS, 3u); + sc_draw_u32_right(65u, 29u, + panel->selected_count ? panel->selected_size : + panel->total_size, + 10u, SC_ATTR_STATUS); + if (panel->selected_count) { + sc_screen_cell(75u, 29u, '/', SC_ATTR_STATUS); + sc_draw_dec3(76u, 29u, panel->selected_count, SC_ATTR_STATUS); + } + if (panel->truncated) sc_screen_cell(79u, 29u, '+', SC_ATTR_DIALOG); +} + +void sc_draw_status(const ScApp *app) __banked +{ + sc_screen_fill(0u, 30u, SC_SCREEN_W, ' ', SC_ATTR_STATUS); + sc_screen_text(1u, 30u, app->status, SC_ATTR_STATUS, 78u); +} + +static void sc_draw_keys(void) +{ + sc_screen_fill(0u, 31u, SC_SCREEN_W, ' ', SC_ATTR_KEYS); + sc_screen_text(0u, 31u, + "1 Help 2 Menu 3 View 4 Edit 5 Copy 6 Ren 7 Mkdir 8 Delete9 Menu 10 Quit", + SC_ATTR_KEYS, SC_SCREEN_W); +} + +void sc_draw_all(const ScApp *app) __banked +{ + uint8_t p; + + sc_screen_clear(SC_ATTR_NORMAL); + for (p = 0u; p < SC_PANEL_COUNT; p++) { + const ScPanel *panel = &app->panels[p]; + uint8_t left = p ? 40u : 0u; + sc_draw_frame(p ? 40u : 0u); + sc_draw_title(app, p); + sc_draw_panel_rows(app, p); + sc_draw_panel_footer(left, panel); + } + sc_draw_info(app); + sc_draw_status(app); + sc_draw_keys(); +} diff --git a/applications/Volkov/src/sc_exec_ui.c b/applications/Volkov/src/sc_exec_ui.c new file mode 100644 index 0000000..82c39ca --- /dev/null +++ b/applications/Volkov/src/sc_exec_ui.c @@ -0,0 +1,207 @@ +/* + * sc_exec_ui.c — запуск EXE из панели и полное восстановление UI. + * + * EXEC синхронен. Пока copy-job не активен, его строковые поля служат + * scratch-буферами пути запуска и сохранённого CWD; это экономит 512 байт + * W2 и безопасно, потому что диспетчер Commander не выполняет jobs параллельно. + */ + +#include +#include +#include +#include +#include +#include "sc_app.h" +#include "sc_exec.h" +#include "sc_exec_ui.h" +#include "sc_path.h" +#include "sc_screen.h" +#include "sc_theme.h" +#include "sc_video.h" + +static char sc_exec_status_text[79]; + +static void sc_exec_status_plain(ScApp *app, const char *text) +{ + char *out = sc_exec_status_text; + char *limit = sc_exec_status_text + sizeof(sc_exec_status_text) - 1u; + + while (*text != '\0' && out < limit) *out++ = *text++; + *out = '\0'; + app->status = sc_exec_status_text; +} + +static uint8_t sc_exec_upper(uint8_t ch) +{ + if (ch >= 'a' && ch <= 'z') return (uint8_t)(ch - ('a' - 'A')); + return ch; +} + +static uint8_t sc_exec_is_exe(const char *name) +{ + uint8_t length = 0u; + + while (name[length] != '\0' && length < 12u) length++; + if (length < 4u || name[length - 4u] != '.') return 0u; + return sc_exec_upper((uint8_t)name[length - 3u]) == 'E' && + sc_exec_upper((uint8_t)name[length - 2u]) == 'X' && + sc_exec_upper((uint8_t)name[length - 1u]) == 'E'; +} + +static char *sc_exec_status_append(char *out, const char *text) +{ + char *limit = sc_exec_status_text + sizeof(sc_exec_status_text) - 1u; + + while (*text != '\0' && out < limit) *out++ = *text++; + *out = '\0'; + return out; +} + +static char *sc_exec_status_uint(char *out, uint16_t value) +{ + char digits[5]; + uint8_t count = 0u; + + do { + digits[count++] = (char)('0' + value % 10u); + value /= 10u; + } while (value != 0u); + while (count != 0u) { + char one[2]; + one[0] = digits[--count]; + one[1] = '\0'; + out = sc_exec_status_append(out, one); + } + return out; +} + +static void sc_exec_status_error(ScApp *app, const char *name, int error) +{ + char *out = sc_exec_status_text; + + out = sc_exec_status_append(out, "Cannot execute "); + out = sc_exec_status_append(out, name); + out = sc_exec_status_append(out, " (errno "); + out = sc_exec_status_uint(out, (uint16_t)error); + (void)sc_exec_status_append(out, ")."); + app->status = sc_exec_status_text; +} + +static void sc_exec_status_unsupported(ScApp *app, const char *name) +{ + char *out = sc_exec_status_text; + + out = sc_exec_status_append(out, "Cannot execute "); + out = sc_exec_status_append(out, name); + (void)sc_exec_status_append(out, ": only .EXE files are supported."); + app->status = sc_exec_status_text; +} + +static void sc_exec_status_success(ScApp *app, int exit_code) +{ + static const char hex[] = "0123456789ABCDEF"; + char *out = sc_exec_status_text; + char code[3]; + + code[0] = hex[((uint8_t)exit_code >> 4) & 0x0Fu]; + code[1] = hex[(uint8_t)exit_code & 0x0Fu]; + code[2] = '\0'; + out = sc_exec_status_append(out, "Program returned "); + out = sc_exec_status_append(out, code); + (void)sc_exec_status_append(out, "; screen, CWD and panels restored."); + app->status = sc_exec_status_text; +} + +static void sc_exec_redraw(ScApp *app) +{ + sc_draw_all(app); + (void)sc_screen_present_full(); +} + +void sc_exec_selected(ScApp *app) __banked +{ + ScPanel *panel = &app->panels[app->active_panel]; + ScDirEntry entry; + ScCopyJob *scratch = &app->copy_job; + uint8_t saved_drive; + int exit_code; + int exec_errno = 0; + int restore_errno = 0; + int video_errno = 0; + int refresh_errno = 0; + uint8_t i; + + if (sc_panel_selected(panel, &entry) != 0 || + (entry.attr & FA_DIREC) != 0u || + (entry.flags & SC_ENTRY_DSS_LIMIT) != 0u) { + sc_exec_status_plain(app, + "Cannot execute: the selected entry is not a file."); + sc_exec_redraw(app); + return; + } + if (!sc_exec_is_exe(entry.name)) { + sc_exec_status_unsupported(app, entry.name); + sc_exec_redraw(app); + return; + } + if (scratch->state == SC_COPY_RUNNING) { + sc_exec_status_plain(app, + "Cannot execute while a copy job is active."); + sc_exec_redraw(app); + return; + } + + saved_drive = getdisk(); + if (!getcwd(scratch->target_path, SC_COPY_PATH_CAPACITY) || + sc_path_join(scratch->source_path, SC_COPY_PATH_CAPACITY, + panel->path, entry.name) != 0) { + sc_exec_status_error(app, entry.name, errno); + sc_exec_redraw(app); + return; + } + + /* Как в классическом Commander, child получает каталог активной панели + * текущим. Это позволяет утилитам без argv открывать соседние файлы. + * Полный путь EXE уже построен; исходный CWD восстановится после EXEC. */ + if (chdir(panel->path) != 0) { + sc_exec_status_error(app, entry.name, errno); + sc_exec_redraw(app); + return; + } + + sc_exec_status_plain(app, + "Starting selected EXE; Commander state is preserved."); + sc_draw_status(app); + (void)sc_screen_present_rows(30u, 1u); + + errno = 0; + exit_code = sc_platform_exec(scratch->source_path); + if (exit_code < 0) exec_errno = errno; + + if (setdisk(saved_drive) < 0) { + restore_errno = errno; + } else if (chdir(scratch->target_path) != 0) { + restore_errno = errno; + } + if (sc_video_restore() != 0) video_errno = errno; + sc_theme_reapply(); + + for (i = 0u; i < SC_PANEL_COUNT; i++) { + if (sc_panel_refresh(&app->panels[i]) != 0 && refresh_errno == 0) { + refresh_errno = errno; + } + } + + if (restore_errno != 0) { + sc_exec_status_error(app, "CWD restore", restore_errno); + } else if (video_errno != 0) { + sc_exec_status_error(app, "video restore", video_errno); + } else if (exit_code < 0) { + sc_exec_status_error(app, entry.name, exec_errno); + } else if (refresh_errno != 0) { + sc_exec_status_error(app, "panel refresh", refresh_errno); + } else { + sc_exec_status_success(app, exit_code); + } + sc_exec_redraw(app); +} diff --git a/applications/Volkov/src/sc_file_ui.c b/applications/Volkov/src/sc_file_ui.c new file mode 100644 index 0000000..ec2139f --- /dev/null +++ b/applications/Volkov/src/sc_file_ui.c @@ -0,0 +1,407 @@ +/* + * sc_file_ui.c — оркестрация безопасных файловых команд версии 0.2. + * + * F7 принимает только один компонент DOS 8.3, строит полный путь в + * резидентном scratch copy job и не меняет глобальный CWD DSS. + */ + +#include +#include +#include +#include +#include "sc_file_ui.h" +#include "sc_file_ops.h" +#include "sc_input.h" +#include "sc_path.h" +#include "sc_screen.h" +#include "sc_store.h" +#include "sc_tree.h" + +#define SC_DELETE_TREE_ERROR (-1) +#define SC_DELETE_TREE_DONE 0 +#define SC_DELETE_TREE_CANCELLED 1 +#define SC_DELETE_DSS_LIMIT 35 + +static void sc_file_redraw(ScApp *app) +{ + sc_draw_all(app); + (void)sc_screen_present_full(); +} + +static uint8_t sc_file_upper(uint8_t ch) +{ + if (ch >= 'a' && ch <= 'z') return (uint8_t)(ch - ('a' - 'A')); + return ch; +} + +static uint8_t sc_file_same_name(const char *left, const char *right) +{ + while (*left != '\0' && *right != '\0') { + if (sc_file_upper((uint8_t)*left) != + sc_file_upper((uint8_t)*right)) return 0u; + left++; + right++; + } + return *left == *right; +} + +static void sc_file_copy_name(char *target, const char *source) +{ + uint8_t i = 0u; + while (i < 12u && source[i] != '\0') { + target[i] = source[i]; + i++; + } + target[i] = '\0'; +} + +static void sc_file_focus_name(ScPanel *panel, const char *name) +{ + ScDirEntry entry; + uint16_t i; + + for (i = 0u; i < panel->count; i++) { + if (sc_store_get(panel, i, &entry) != 0) break; + if (sc_file_same_name(entry.name, name)) { + panel->cursor = i; + sc_panel_normalize_view(panel); + return; + } + } +} + +void sc_mkdir_command(ScApp *app) __banked +{ + ScPanel *panel = &app->panels[app->active_panel]; + char *name = app->copy_job.source_path; + char *path = app->copy_job.target_path; + + name[0] = '\0'; + if (!sc_dialog_input(" Make directory ", "New DOS 8.3 name:", + name, 13u)) { + app->status = "Create directory cancelled."; + sc_file_redraw(app); + return; + } + if (sc_path_validate_component(name) != 0 || + sc_path_join(path, SC_COPY_PATH_CAPACITY, panel->path, name) != 0) { + app->status = "Invalid directory name; one DOS 8.3 component required."; + sc_file_redraw(app); + return; + } + + errno = 0; + if (mkdir(path) != 0) { + app->status = (errno == EEXIST || errno == EISDIR) ? + "Cannot create directory: it already exists." : + "Cannot create directory; DSS rejected the request."; + sc_file_redraw(app); + return; + } + + if (sc_panel_refresh(panel) != 0) { + app->status = "Directory created, but panel refresh failed."; + } else { + app->status = "Directory created; active panel refreshed."; + } + sc_file_redraw(app); +} + +void sc_rename_command(ScApp *app) __banked +{ + ScPanel *panel = &app->panels[app->active_panel]; + ScDirEntry entry; + char *new_name = app->copy_job.source_path; + int result; + int saved_errno; + + if (sc_panel_selected(panel, &entry) != 0 || + (entry.flags & (SC_ENTRY_PARENT | SC_ENTRY_DSS_LIMIT))) { + app->status = "This virtual entry cannot be renamed."; + sc_file_redraw(app); + return; + } + if (panel->selected_count != 0u && + !(panel->selected_count == 1u && + (entry.flags & SC_ENTRY_SELECTED))) { + app->status = "Group move/rename is not implemented yet."; + sc_file_redraw(app); + return; + } + + sc_file_copy_name(new_name, entry.name); + if (!sc_dialog_input(" Rename ", "New DOS 8.3 name:", + new_name, 13u)) { + app->status = "Rename cancelled."; + sc_file_redraw(app); + return; + } + if (sc_path_validate_component(new_name) != 0) { + app->status = "Invalid name; one DOS 8.3 component required."; + sc_file_redraw(app); + return; + } + if (sc_file_same_name(entry.name, new_name)) { + app->status = "Rename skipped: the name is unchanged."; + sc_file_redraw(app); + return; + } + + errno = 0; + result = sc_rename_local(panel->path, entry.name, new_name, + app->copy_job.temp_path, + SC_COPY_PATH_CAPACITY); + saved_errno = errno; + if (result == -2) { + app->status = "Rename attempted, but DSS CWD restore failed."; + sc_file_redraw(app); + return; + } + if (result != 0) { + app->status = (saved_errno == EEXIST || saved_errno == EISDIR) ? + "Cannot rename: target already exists." : + "Cannot rename; DSS rejected the request."; + sc_file_redraw(app); + return; + } + + if (sc_panel_refresh(panel) != 0) { + app->status = "Renamed, but panel refresh failed."; + } else { + sc_file_focus_name(panel, new_name); + app->status = "Renamed in active directory."; + } + sc_file_redraw(app); +} + +static int sc_file_count_selected(const ScPanel *panel, + uint16_t *files, + uint16_t *directories) +{ + ScDirEntry entry; + uint16_t i; + + *files = 0u; + *directories = 0u; + for (i = 0u; i < panel->count; i++) { + if (sc_store_get(panel, i, &entry) != 0) return -1; + if (!(entry.flags & SC_ENTRY_SELECTED)) continue; + if (entry.attr & FA_DIREC) (*directories)++; + else if (!(entry.flags & SC_ENTRY_DSS_LIMIT)) (*files)++; + } + return 0; +} + +static void sc_file_redraw_info(ScApp *app) +{ + sc_draw_info(app); + sc_draw_status(app); + (void)sc_screen_present_rows(29u, 2u); +} + +static int sc_delete_tree(ScApp *app, ScPanel *panel, + uint16_t *deleted_files, + uint16_t *deleted_directories, + int *operation_error) +{ + ScTreeJob tree; + ScDirEntry entry; + ScKeyEvent event; + uint16_t i; + int step = SC_TREE_STEP_DONE; + int result = SC_DELETE_TREE_DONE; + + if (sc_tree_begin(&tree, &app->copy_job) != 0) { + *operation_error = errno; + return SC_DELETE_TREE_ERROR; + } + for (i = 0u; i < panel->count; i++) { + if (sc_store_get(panel, i, &entry) != 0) { + *operation_error = ENOMEM; + step = SC_TREE_STEP_ERROR; + result = SC_DELETE_TREE_ERROR; + break; + } + if (!(entry.flags & SC_ENTRY_SELECTED) || + !(entry.attr & FA_DIREC) || + (entry.flags & (SC_ENTRY_PARENT | SC_ENTRY_DSS_LIMIT))) continue; + if (sc_tree_add_delete_root(&tree, entry.name) != 0) { + *operation_error = errno; + step = SC_TREE_STEP_ERROR; + result = SC_DELETE_TREE_ERROR; + break; + } + } + + while (step != SC_TREE_STEP_ERROR) { + if (sc_input_poll(&event) && event.ascii == 0x1Bu) { + result = SC_DELETE_TREE_CANCELLED; + break; + } + step = sc_tree_delete_next(&tree, &app->copy_job, + panel->path, &entry); + if (step == SC_TREE_STEP_DONE) break; + if (step == SC_TREE_STEP_ERROR) { + *operation_error = tree.error; + result = SC_DELETE_TREE_ERROR; + break; + } + if (step == SC_TREE_STEP_SCAN_DIR) { + app->status = "Scanning directory tree for delete; Esc cancels."; + sc_file_redraw_info(app); + } else if (step == SC_TREE_STEP_FILE) { + if (unlink(app->copy_job.target_path) != 0) { + *operation_error = errno; + result = SC_DELETE_TREE_ERROR; + break; + } + (*deleted_files)++; + } else { + if (rmdir(app->copy_job.target_path) != 0) { + *operation_error = errno; + result = SC_DELETE_TREE_ERROR; + break; + } + (*deleted_directories)++; + } + } + + if (sc_tree_finish(&tree, &app->copy_job) != 0 && + *operation_error == 0) { + *operation_error = errno; + result = SC_DELETE_TREE_ERROR; + } + return result; +} + +static void sc_delete_group(ScApp *app, ScPanel *panel) +{ + ScDirEntry entry; + char *path = app->copy_job.target_path; + uint16_t files; + uint16_t directories; + uint16_t deleted = 0u; + uint16_t deleted_directories = 0u; + uint16_t i; + uint8_t failed = 0u; + uint8_t cancelled = 0u; + int operation_error = 0; + + if (sc_file_count_selected(panel, &files, &directories) != 0) { + app->status = "Group delete failed: EMM access error."; + sc_file_redraw(app); + return; + } + if (files == 0u && directories == 0u) { + app->status = "No real selected entries to delete."; + sc_file_redraw(app); + return; + } + if (!sc_dialog_confirm(" Delete group ", + directories ? + "Delete selected files and trees?" : + "Delete all selected files?")) { + app->status = "Group delete cancelled; nothing changed."; + sc_file_redraw(app); + return; + } + + for (i = 0u; i < panel->count; i++) { + if (sc_store_get(panel, i, &entry) != 0) { + failed = 1u; + operation_error = ENOMEM; + break; + } + if (!(entry.flags & SC_ENTRY_SELECTED) || + (entry.attr & FA_DIREC) || + (entry.flags & SC_ENTRY_DSS_LIMIT)) continue; + if (sc_path_join(path, SC_COPY_PATH_CAPACITY, + panel->path, entry.name) != 0) { + failed = 1u; + operation_error = errno; + break; + } + if (unlink(path) != 0) { + failed = 1u; + operation_error = errno; + break; + } + deleted++; + } + + if (!failed && directories != 0u) { + int tree_result = sc_delete_tree(app, panel, &deleted, + &deleted_directories, + &operation_error); + if (tree_result == SC_DELETE_TREE_CANCELLED) cancelled = 1u; + else if (tree_result == SC_DELETE_TREE_ERROR) failed = 1u; + } + + if ((deleted != 0u || deleted_directories != 0u) && + sc_panel_refresh(panel) != 0) { + app->status = "Objects deleted, but panel refresh failed."; + } else if (cancelled) { + app->status = "Recursive delete cancelled; completed changes remain."; + } else if (failed) { + app->status = operation_error == SC_DELETE_DSS_LIMIT ? + "Recursive delete stopped at DSS directory limit; changes remain." : + "Group delete stopped on first error; completed changes remain."; + } else if (directories != 0u) { + app->status = "Selected files and directory trees deleted."; + } else { + app->status = "Selected files deleted."; + } + sc_file_redraw(app); +} + +void sc_delete_command(ScApp *app) __banked +{ + ScPanel *panel = &app->panels[app->active_panel]; + ScDirEntry entry; + char *path = app->copy_job.target_path; + int result; + int saved_errno; + + if (panel->selected_count != 0u) { + sc_delete_group(app, panel); + return; + } + if (sc_panel_selected(panel, &entry) != 0 || + (entry.flags & (SC_ENTRY_PARENT | SC_ENTRY_DSS_LIMIT))) { + app->status = "This virtual entry cannot be deleted."; + sc_file_redraw(app); + return; + } + if (sc_path_join(path, SC_COPY_PATH_CAPACITY, + panel->path, entry.name) != 0) { + app->status = "Cannot build the path to delete."; + sc_file_redraw(app); + return; + } + if (!sc_dialog_confirm(" Delete ", + (entry.attr & FA_DIREC) ? + "Delete this empty directory?" : + "Delete this file?")) { + app->status = "Delete cancelled; nothing changed."; + sc_file_redraw(app); + return; + } + + errno = 0; + result = (entry.attr & FA_DIREC) ? rmdir(path) : unlink(path); + saved_errno = errno; + if (result != 0) { + app->status = (saved_errno == ENOTEMPTY) ? + "Cannot delete: directory is not empty." : + "Cannot delete; DSS rejected the request."; + sc_file_redraw(app); + return; + } + if (sc_panel_refresh(panel) != 0) { + app->status = "Deleted, but panel refresh failed."; + } else { + app->status = (entry.attr & FA_DIREC) ? + "Empty directory deleted." : "File deleted."; + } + sc_file_redraw(app); +} diff --git a/applications/Volkov/src/sc_input.c b/applications/Volkov/src/sc_input.c new file mode 100644 index 0000000..7d8849d --- /dev/null +++ b/applications/Volkov/src/sc_input.c @@ -0,0 +1,35 @@ +/* sc_input.c — нормализация getkey() и live-модификаторов DSS. */ + +#include +#include +#include "sc_input.h" + +static uint8_t sc_compact_modifiers(uint16_t state) +{ + uint8_t result = 0u; + if (state & (KBD_MOD_LSHIFT | KBD_MOD_RSHIFT)) result |= SC_MOD_SHIFT; + if (state & (KBD_MOD_CTRL | KBD_MOD_LCTRL | KBD_MOD_RCTRL)) { + result |= SC_MOD_CTRL; + } + if (state & (KBD_MOD_ALT | KBD_MOD_LALT | KBD_MOD_RALT)) { + result |= SC_MOD_ALT; + } + return result; +} + +void sc_input_read(ScKeyEvent *event) +{ + uint16_t key = getkey(); + uint16_t state = kbd_mod_state(); + + event->ascii = (uint8_t)key; + event->scan = (uint8_t)(key >> 8) & 0x7Fu; + event->modifiers = sc_compact_modifiers(state); +} + +uint8_t sc_input_poll(ScKeyEvent *event) +{ + if (!kbhit()) return 0u; + sc_input_read(event); + return 1u; +} diff --git a/applications/Volkov/src/sc_metadata.c b/applications/Volkov/src/sc_metadata.c new file mode 100644 index 0000000..fbdaf2d --- /dev/null +++ b/applications/Volkov/src/sc_metadata.c @@ -0,0 +1,143 @@ +/* + * sc_metadata.c — запись FAT date/time и атрибутов через DSS. + * + * PUT_D_T ($18) принимает раскрытые поля даты/времени по открытому handle; + * изменение фиксируется при CLOSE. ATTRIB ($16) вызывается после commit и + * получает полный путь. Оба wrapper сохраняют IX и переводят CF/A в errno. + */ + +#include +#include +#include "sc_metadata.h" + +typedef struct { + uint8_t day; + uint8_t month; + uint16_t year; + uint8_t hour; + uint8_t minute; + uint8_t second; +} ScMetadataDateTime; + +static int sc_metadata_put_raw(uint8_t fd, + const ScMetadataDateTime *value) __naked +{ + (void)fd; + (void)value; + __asm + ;; __sdcccall(1): fd в A, value в DE. + push ix ; IX caller + push af ; handle + push de + pop ix ; IX = value + + ld c, 2 (ix) ; год сначала сохраняем на стеке + ld b, 3 (ix) + push bc + ld d, 0 (ix) ; день + ld e, 1 (ix) ; месяц + ld h, 4 (ix) ; час + ld l, 5 (ix) ; минуты + ld b, 6 (ix) ; секунды + pop ix ; IX = год + pop af ; A = handle + ld c, #0x18 ; ESTEX PUT_D_T + rst #0x10 + jr c, _sc_metadata_put_error + pop ix + ld de, #0 + ret + _sc_metadata_put_error: + pop ix + call __errno_set + ld de, #-1 + ret + __endasm; +} + +int sc_metadata_set_datetime(uint8_t fd, uint16_t date, + uint16_t time) __banked +{ + ScMetadataDateTime value; + + value.day = (uint8_t)(date & 0x1Fu); + value.month = (uint8_t)((date >> 5) & 0x0Fu); + value.year = (uint16_t)(1980u + (date >> 9)); + value.hour = (uint8_t)(time >> 11); + value.minute = (uint8_t)((time >> 5) & 0x3Fu); + value.second = (uint8_t)((time & 0x1Fu) << 1); + + if (value.day == 0u || value.month == 0u || value.month > 12u || + value.hour > 23u || value.minute > 59u || value.second > 59u) { + errno = EINVAL; + return -1; + } + return sc_metadata_put_raw(fd, &value); +} + +static int sc_metadata_attr_raw(const char *path, uint8_t attr) __naked +{ + (void)path; + (void)attr; + __asm + ;; __sdcccall(1): path в HL, 8-битный attr в [SP+2]. + ld iy, #2 + add iy, sp + ld a, 0 (iy) + push ix + and #0x27 ; readonly/hidden/system/archive + ld b, #1 ; SET + ld c, #0x16 ; ESTEX ATTRIB + rst #0x10 + pop ix + pop hl ; адрес возврата + inc sp ; снять однобайтовый attr + jr c, _sc_metadata_attr_raw_error + ld de, #0 + jp (hl) + _sc_metadata_attr_raw_error: + call __errno_set + ld de, #-1 + jp (hl) + __endasm; +} + +static int sc_metadata_attr_get_raw(const char *path, + uint8_t *attr) __naked +{ + (void)path; + (void)attr; + __asm + ;; __sdcccall(1): path в HL, attr в DE. DSS клобберит регистры, + ;; поэтому near-указатель переживает RST на стеке. + push ix + push de + ld b, #0 ; GET + ld c, #0x16 ; ESTEX ATTRIB + rst #0x10 + pop de + pop ix + jr c, _sc_metadata_attr_get_error + ld (de), a + ld de, #0 + ret + _sc_metadata_attr_get_error: + call __errno_set + ld de, #-1 + ret + __endasm; +} + +int sc_metadata_get_attributes(const char *path, uint8_t *attr) __banked +{ + if (!path || !attr) { + errno = EINVAL; + return -1; + } + return sc_metadata_attr_get_raw(path, attr); +} + +int sc_metadata_set_attributes(const char *path, uint8_t attr) __banked +{ + return sc_metadata_attr_raw(path, (uint8_t)(attr & 0x27u)); +} diff --git a/applications/Volkov/src/sc_name.c b/applications/Volkov/src/sc_name.c new file mode 100644 index 0000000..e6730cd --- /dev/null +++ b/applications/Volkov/src/sc_name.c @@ -0,0 +1,53 @@ +/* + * sc_name.c — строгая проверка одного пользовательского компонента DOS 8.3. + * + * Разделители, wildcard и неоднозначные `.`/`..` запрещены до вызова DSS; + * CP866-байты 80h..FFh разрешены как буквы имени. + */ + +#include +#include +#include "sc_path.h" + +static uint8_t sc_name_char(uint8_t ch) +{ + if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || + (ch >= '0' && ch <= '9') || ch >= 0x80u) return 1u; + switch (ch) { + case '_': case '-': case '$': case '~': case '!': case '#': + case '%': case '&': case '\'': case '(': case ')': case '@': + case '^': case '`': case '{': case '}': + return 1u; + default: + return 0u; + } +} + +int sc_path_validate_component(const char *name) __banked +{ + uint8_t base = 0u; + uint8_t extension = 0u; + uint8_t have_dot = 0u; + uint8_t ch; + + if (!name || name[0] == '\0') goto invalid; + while ((ch = (uint8_t)*name++) != 0u) { + if (ch == '.') { + if (have_dot || base == 0u) goto invalid; + have_dot = 1u; + } else { + if (!sc_name_char(ch)) goto invalid; + if (have_dot) { + if (++extension > 3u) goto invalid; + } else if (++base > 8u) { + goto invalid; + } + } + } + if (base == 0u || (have_dot && extension == 0u)) goto invalid; + return 0; + +invalid: + errno = EINAME; + return -1; +} diff --git a/applications/Volkov/src/sc_panel.c b/applications/Volkov/src/sc_panel.c new file mode 100644 index 0000000..49135d7 --- /dev/null +++ b/applications/Volkov/src/sc_panel.c @@ -0,0 +1,63 @@ +/* sc_panel.c — инварианты панели и переходы по настоящим каталогам. */ + +#include +#include +#include +#include "sc_panel.h" +#include "sc_source.h" +#include "sc_store.h" + +void sc_panel_normalize_view(ScPanel *panel) +{ + if (panel->count == 0u) { + panel->cursor = 0u; + panel->top = 0u; + return; + } + if (panel->cursor >= panel->count) panel->cursor = panel->count - 1u; + if (panel->top > panel->cursor) panel->top = panel->cursor; + if (panel->cursor >= (uint16_t)(panel->top + 27u)) { + panel->top = (uint16_t)(panel->cursor - 26u); + } +} + +int sc_panel_init(ScPanel *panel, uint8_t phys_page) +{ + panel->phys_page = phys_page; + panel->source_kind = SC_SOURCE_REAL_FS; + panel->count = 0u; + panel->cursor = 0u; + panel->top = 0u; + panel->total_size = 0ul; + panel->truncated = 0u; + panel->dirty = 1u; + panel->sort_key = SC_SORT_NAME; + panel->sort_descending = 0u; + if (!getcwd(panel->path, (uint16_t)sizeof(panel->path))) return -1; + return sc_source_fs_scan(panel); +} + +int sc_panel_refresh(ScPanel *panel) +{ + return sc_source_fs_scan(panel); +} + +int sc_panel_selected(const ScPanel *panel, ScDirEntry *entry) +{ + if (panel->count == 0u) return -1; + return sc_store_get(panel, panel->cursor, entry); +} + +int sc_panel_open_selected(ScPanel *panel) +{ + ScDirEntry entry; + if (sc_panel_selected(panel, &entry) != 0) return -1; + if (entry.flags & SC_ENTRY_DSS_LIMIT) return 2; + if (!(entry.attr & FA_DIREC)) return 1; + return sc_source_fs_change_dir(panel, entry.name); +} + +int sc_panel_parent(ScPanel *panel) +{ + return sc_source_fs_change_dir(panel, ".."); +} diff --git a/applications/Volkov/src/sc_path.c b/applications/Volkov/src/sc_path.c new file mode 100644 index 0000000..16fa04c --- /dev/null +++ b/applications/Volkov/src/sc_path.c @@ -0,0 +1,40 @@ +/* + * sc_path.c — построение завершённого абсолютного/корневого пути. + * + * Функция не предполагает наличие или отсутствие завершающего `\` у + * каталога и до записи имени проверяет вместимость всего результата. + */ + +#include +#include +#include "sc_path.h" + +int sc_path_join(char *out, uint16_t capacity, + const char *directory, const char *name) +{ + uint16_t dir_len = 0u; + uint16_t name_len = 0u; + uint16_t pos; + uint8_t need_separator; + + if (!out || !directory || !name || capacity == 0u || name[0] == '\0') { + errno = EINVAL; + return -1; + } + while (directory[dir_len] != '\0') dir_len++; + while (name[name_len] != '\0') name_len++; + need_separator = dir_len != 0u && directory[dir_len - 1u] != '\\' && + directory[dir_len - 1u] != '/'; + if (dir_len + (uint16_t)need_separator + name_len + 1u > capacity) { + errno = EINAME; + return -1; + } + + for (pos = 0u; pos < dir_len; pos++) out[pos] = directory[pos]; + if (need_separator) out[pos++] = '\\'; + for (name_len = 0u; name[name_len] != '\0'; name_len++) { + out[pos++] = name[name_len]; + } + out[pos] = '\0'; + return 0; +} diff --git a/applications/Volkov/src/sc_platform_exec.c b/applications/Volkov/src/sc_platform_exec.c new file mode 100644 index 0000000..8dc9c9f --- /dev/null +++ b/applications/Volkov/src/sc_platform_exec.c @@ -0,0 +1,30 @@ +/* + * sc_platform_exec.c — минимальная обёртка ESTEX EXEC $40. + * + * Вход: HL = ASCIIZ-путь в W1..W3. B=1 сообщает DSS, что передан явный + * путь. На успехе функция возвращает код завершения 0..255 как int в DE; + * на ошибке — -1 и errno. IX сохраняется согласно ABI SDCC. + */ + +#include +#include "sc_exec.h" + +int sc_platform_exec(const char *path) __naked +{ + (void)path; + __asm + push ix + ld b, #1 + ld c, #0x40 + rst #0x10 + pop ix + jr c, _sc_platform_exec_error + ld e, a + ld d, #0 + ret + _sc_platform_exec_error: + call __errno_set + ld de, #-1 + ret + __endasm; +} diff --git a/applications/Volkov/src/sc_platform_screen.c b/applications/Volkov/src/sc_platform_screen.c new file mode 100644 index 0000000..afc0845 --- /dev/null +++ b/applications/Volkov/src/sc_platform_screen.c @@ -0,0 +1,54 @@ +/* + * sc_platform_screen.c — app-local wrapper ESTEX WINREST 5Ah. + * + * ABI SDCC __sdcccall(1): row в A, col в L, остальные аргументы на стеке. + * IX callee-saved. Источник — физическая EMM-страница B и 0xC000+offset. + */ + +#include +#include "sc_video.h" + +void sc_platform_winrest(uint8_t row, uint8_t col, + uint8_t height, uint8_t width, + uint8_t page, uint16_t offset) __naked +{ + (void)row; + (void)col; + (void)height; + (void)width; + (void)page; + (void)offset; + __asm + ld iy, #2 + add iy, sp + ld d, a + ld e, l + + push ix + ld l, 3 (iy) + ld h, 4 (iy) + ld bc, #0xC000 + add hl, bc + push hl + pop ix + + ld h, 0 (iy) + ld a, 1 (iy) + ld l, a + ld a, 2 (iy) + ld b, a + ld c, #0x5A + di + rst #0x10 + ei + pop ix + + pop hl + inc sp + inc sp + inc sp + inc sp + inc sp + jp (hl) + __endasm; +} diff --git a/applications/Volkov/src/sc_rename.c b/applications/Volkov/src/sc_rename.c new file mode 100644 index 0000000..a6292b7 --- /dev/null +++ b/applications/Volkov/src/sc_rename.c @@ -0,0 +1,55 @@ +/* + * sc_rename.c — локальный RENAME DSS с обязательным возвратом CWD. + * + * DSS 1.71 принимает два basename только относительно текущего каталога. + * Все строки и scratch принадлежат вызывающему W2-коду; банковый модуль не + * публикует указателей на свои данные и не хранит глобального состояния. + */ + +#include +#include +#include +#include +#include +#include "sc_file_ops.h" + +#define SC_RENAME_FIND_ATTRS \ + (FA_RDONLY | FA_HIDDEN | FA_SYSTEM | FA_DIREC | FA_ARCH) + +int sc_rename_local(const char *directory, + const char *old_name, + const char *new_name, + char *cwd_buffer, + uint16_t cwd_capacity) __banked +{ + ffblk_t found; + int operation_result; + int operation_errno; + + if (!directory || !old_name || !new_name || !cwd_buffer || + old_name[0] == '\0' || new_name[0] == '\0' || cwd_capacity < 2u) { + errno = EINVAL; + return -1; + } + if (!getcwd(cwd_buffer, cwd_capacity)) return -1; + if (chdir(directory) != 0) return -1; + + errno = 0; + if (ffirst(new_name, &found, SC_RENAME_FIND_ATTRS) == 0) { + operation_result = -1; + operation_errno = EEXIST; + } else if (errno != ENOENT) { + operation_result = -1; + operation_errno = errno; + } else { + errno = 0; + operation_result = rename(old_name, new_name); + operation_errno = errno; + } + if (chdir(cwd_buffer) != 0) return -2; + if (operation_result != 0) { + errno = operation_errno ? operation_errno : EUNKERR; + return -1; + } + return 0; +} diff --git a/applications/Volkov/src/sc_screen.c b/applications/Volkov/src/sc_screen.c new file mode 100644 index 0000000..c921f6e --- /dev/null +++ b/applications/Volkov/src/sc_screen.c @@ -0,0 +1,176 @@ +/* + * sc_screen.c — единственный владелец экранной EMM-модели. + * + * Указатель 0xC000 существует только внутри короткой операции; исходное + * отображение W3 восстанавливается до возврата вызывающему коду. + */ + +#include +#include +#include +#include "sc_screen.h" +#include "sc_video.h" + +static uint8_t sc_screen_page; +static uint8_t sc_screen_attached; + +static uint16_t sc_cell_offset(uint8_t x, uint8_t y) +{ + return (uint16_t)y * SC_ROW_BYTES + (uint16_t)x * 2u; +} +void sc_screen_attach(uint8_t page) +{ + sc_screen_page = page; + sc_screen_attached = 1u; +} + +void sc_screen_detach(void) +{ + sc_screen_attached = 0u; +} + +void sc_screen_clear(uint8_t attr) +{ + uint8_t saved; + ScCell *cells; + uint16_t i; + + if (!sc_screen_attached) return; + saved = _io_page_w3; + sprinter_page_w3(sc_screen_page); + cells = (ScCell *)0xC000u; + for (i = 0u; i < (uint16_t)SC_SCREEN_W * SC_SCREEN_H; i++) { + cells[i].ch = ' '; + cells[i].attr = attr; + } + sprinter_page_w3(saved); +} + +void sc_screen_cell(uint8_t x, uint8_t y, uint8_t ch, uint8_t attr) +{ + uint8_t saved; + ScCell *cell; + + if (!sc_screen_attached || x >= SC_SCREEN_W || y >= SC_SCREEN_H) return; + saved = _io_page_w3; + sprinter_page_w3(sc_screen_page); + cell = (ScCell *)(0xC000u + sc_cell_offset(x, y)); + cell->ch = ch; + cell->attr = attr; + sprinter_page_w3(saved); +} + +void sc_screen_fill(uint8_t x, uint8_t y, uint8_t width, + uint8_t ch, uint8_t attr) +{ + uint8_t saved; + ScCell *cell; + uint8_t i; + + if (!sc_screen_attached || y >= SC_SCREEN_H || x >= SC_SCREEN_W) return; + if ((uint16_t)x + width > SC_SCREEN_W) width = (uint8_t)(SC_SCREEN_W - x); + + saved = _io_page_w3; + sprinter_page_w3(sc_screen_page); + cell = (ScCell *)(0xC000u + sc_cell_offset(x, y)); + for (i = 0u; i < width; i++) { + cell[i].ch = ch; + cell[i].attr = attr; + } + sprinter_page_w3(saved); +} + +void sc_screen_text(uint8_t x, uint8_t y, const char *text, + uint8_t attr, uint8_t max_width) +{ + uint8_t saved; + ScCell *cell; + uint8_t i = 0u; + + if (!sc_screen_attached || !text || y >= SC_SCREEN_H || + x >= SC_SCREEN_W) return; + if ((uint16_t)x + max_width > SC_SCREEN_W) { + max_width = (uint8_t)(SC_SCREEN_W - x); + } + + saved = _io_page_w3; + sprinter_page_w3(sc_screen_page); + cell = (ScCell *)(0xC000u + sc_cell_offset(x, y)); + while (text[i] && i < max_width) { + cell[i].ch = (uint8_t)text[i]; + cell[i].attr = attr; + i++; + } + sprinter_page_w3(saved); +} + +int sc_screen_present_full(void) +{ + if (!sc_screen_attached) return -1; + return sc_video_present_rect(sc_screen_page, 0u, 0u, 0u, + SC_SCREEN_H, SC_SCREEN_W); +} + +int sc_screen_present_row(uint8_t x, uint8_t y, uint8_t width) +{ + if (!sc_screen_attached || y >= SC_SCREEN_H || x >= SC_SCREEN_W || + width == 0u || (uint16_t)x + width > SC_SCREEN_W) return -1; + return sc_video_present_rect(sc_screen_page, sc_cell_offset(x, y), + y, x, 1u, width); +} + +int sc_screen_present_rows(uint8_t first, uint8_t count) +{ + if (!sc_screen_attached || first >= SC_SCREEN_H || count == 0u || + (uint16_t)first + count > SC_SCREEN_H) return -1; + return sc_video_present_rect(sc_screen_page, + (uint16_t)first * SC_ROW_BYTES, + first, 0u, count, SC_SCREEN_W); +} + +int sc_screen_scroll_rows(uint8_t x, uint8_t y, + uint8_t width, uint8_t height, + uint8_t direction, uint8_t fill_attr) +{ + uint8_t saved; + uint8_t i; + uint8_t j; + ScCell *model; + ScCell *open_row; + + if (!sc_screen_attached || width == 0u || height < 2u || + x >= SC_SCREEN_W || y >= SC_SCREEN_H || + (uint16_t)x + width > SC_SCREEN_W || + (uint16_t)y + height > SC_SCREEN_H || + (direction != SC_SCROLL_UP && direction != SC_SCROLL_DOWN)) return -1; + + saved = _io_page_w3; + sprinter_page_w3(sc_screen_page); + model = (ScCell *)0xC000u; + + if (direction == SC_SCROLL_UP) { + for (i = 0u; i + 1u < height; i++) { + memmove(model + (uint16_t)(y + i) * SC_SCREEN_W + x, + model + (uint16_t)(y + i + 1u) * SC_SCREEN_W + x, + (uint16_t)width * sizeof(ScCell)); + } + open_row = model + (uint16_t)(y + height - 1u) * SC_SCREEN_W + x; + } else { + i = (uint8_t)(height - 1u); + while (i != 0u) { + memmove(model + (uint16_t)(y + i) * SC_SCREEN_W + x, + model + (uint16_t)(y + i - 1u) * SC_SCREEN_W + x, + (uint16_t)width * sizeof(ScCell)); + i--; + } + open_row = model + (uint16_t)y * SC_SCREEN_W + x; + } + + for (j = 0u; j < width; j++) { + open_row[j].ch = ' '; + open_row[j].attr = fill_attr; + } + sprinter_page_w3(saved); + + return sc_video_scroll_rect(y, x, height, width, direction); +} diff --git a/applications/Volkov/src/sc_select.c b/applications/Volkov/src/sc_select.c new file mode 100644 index 0000000..15b6c7a --- /dev/null +++ b/applications/Volkov/src/sc_select.c @@ -0,0 +1,130 @@ +/* + * sc_select.c — флаги выбора непосредственно в EMM-store панели. + * + * `..` и виртуальный маркер DSS никогда не выбираются. Insert может + * выбрать каталог, но групповые `+`, `-` и `*` работают только с файлами. + * Размер выбранного каталога пока нулевой; его рекурсивный размер будет считать job. + * Долгоживущих указателей W3 нет. + */ + +#include +#include +#include "sc_select.h" +#include "sc_store.h" + +static uint8_t sc_select_allowed(const ScDirEntry *entry) +{ + return (entry->flags & (SC_ENTRY_PARENT | SC_ENTRY_DSS_LIMIT)) == 0u; +} + +static uint8_t sc_select_group_allowed(const ScDirEntry *entry) +{ + return sc_select_allowed(entry) && !(entry->attr & FA_DIREC); +} + +static uint8_t sc_select_upper(uint8_t ch) +{ + if (ch >= 'a' && ch <= 'z') return (uint8_t)(ch - ('a' - 'A')); + return ch; +} + +static uint8_t sc_select_mask_match(const char *pattern, const char *name) +{ + const char *star = 0; + const char *retry = 0; + + /* DOS `*.*` означает все имена файлов, включая имена без точки. */ + if (pattern[0] == '*' && pattern[1] == '.' && pattern[2] == '*' && + pattern[3] == '\0') pattern = "*"; + + while (*name != '\0') { + if (*pattern == '?' || + sc_select_upper((uint8_t)*pattern) == + sc_select_upper((uint8_t)*name)) { + pattern++; + name++; + } else if (*pattern == '*') { + star = pattern++; + retry = name; + } else if (star) { + pattern = star + 1; + name = ++retry; + } else { + return 0u; + } + } + while (*pattern == '*') pattern++; + return *pattern == '\0'; +} + +static int sc_select_toggle(ScPanel *panel) +{ + ScDirEntry entry; + uint8_t was_selected; + + if (panel->count == 0u || + sc_store_get(panel, panel->cursor, &entry) != 0) return -1; + if (!sc_select_allowed(&entry)) return 1; + + was_selected = (entry.flags & SC_ENTRY_SELECTED) != 0u; + entry.flags ^= SC_ENTRY_SELECTED; + if (sc_store_put(panel, panel->cursor, &entry) != 0) return -1; + + if (was_selected) { + if (panel->selected_count != 0u) panel->selected_count--; + if (!(entry.attr & FA_DIREC)) panel->selected_size -= entry.size; + } else { + panel->selected_count++; + if (!(entry.attr & FA_DIREC)) panel->selected_size += entry.size; + } + + if (panel->cursor + 1u < panel->count) panel->cursor++; + sc_panel_normalize_view(panel); + panel->dirty = 1u; + return 0; +} + +static int sc_select_many(ScPanel *panel, uint8_t action, + const char *mask) +{ + ScDirEntry entry; + uint16_t i; + uint16_t selected_count = 0u; + uint32_t selected_size = 0ul; + + for (i = 0u; i < panel->count; i++) { + if (sc_store_get(panel, i, &entry) != 0) return -1; + if (sc_select_group_allowed(&entry)) { + uint8_t old_flags = entry.flags; + if (action == SC_SELECT_INVERT) { + entry.flags ^= SC_ENTRY_SELECTED; + } else if (sc_select_mask_match(mask, entry.name)) { + if (action == SC_SELECT_MASK_SET) { + entry.flags |= SC_ENTRY_SELECTED; + } else { + entry.flags &= (uint8_t)~SC_ENTRY_SELECTED; + } + } + if (entry.flags != old_flags && + sc_store_put(panel, i, &entry) != 0) return -1; + } + if (sc_select_allowed(&entry) && + (entry.flags & SC_ENTRY_SELECTED)) { + selected_count++; + if (!(entry.attr & FA_DIREC)) selected_size += entry.size; + } + } + panel->selected_count = selected_count; + panel->selected_size = selected_size; + panel->dirty = 1u; + return 0; +} + +int sc_select_apply(ScPanel *panel, uint8_t action, + const char *mask) __banked +{ + if (action == SC_SELECT_TOGGLE) return sc_select_toggle(panel); + if (action > SC_SELECT_MASK_CLEAR || + (action != SC_SELECT_INVERT && !mask)) return -1; + return sc_select_many(panel, action, mask); +} diff --git a/applications/Volkov/src/sc_sort.c b/applications/Volkov/src/sc_sort.c new file mode 100644 index 0000000..87cb77c --- /dev/null +++ b/applications/Volkov/src/sc_sort.c @@ -0,0 +1,164 @@ +/* + * sc_sort.c — нерекурсивная Shell sort записей непосредственно в EMM. + * Порядок: синтетический `..`, каталоги, файлы, предупреждение о пределе + * DSS. Внутри каталогов и файлов действует независимый режим панели: + * name/ext/size/date и прямое/обратное направление. Callback qsort и + * долгоживущие W3-указатели не нужны. + */ + +#include +#include +#include +#include "sc_sort.h" +#include "sc_store.h" + +static uint8_t sc_ascii_upper(uint8_t ch) +{ + if (ch >= 'a' && ch <= 'z') return (uint8_t)(ch - ('a' - 'A')); + return ch; +} + +static uint8_t sc_entry_group(const ScDirEntry *entry) +{ + if (entry->flags & SC_ENTRY_PARENT) return 0u; + if (entry->flags & SC_ENTRY_DSS_LIMIT) return 3u; + if (entry->attr & FA_DIREC) return 1u; + return 2u; +} + +static int sc_compare_text(const char *left, const char *right) +{ + uint8_t i; + + for (i = 0u; i < 13u; i++) { + uint8_t a = sc_ascii_upper((uint8_t)left[i]); + uint8_t b = sc_ascii_upper((uint8_t)right[i]); + if (a != b) return a < b ? -1 : 1; + if (a == 0u) break; + } + return 0; +} + +static const char *sc_extension(const char *name) +{ + const char *result = &name[12]; + uint8_t i; + + for (i = 0u; i < 13u && name[i] != '\0'; i++) { + if (name[i] == '.') result = &name[i + 1u]; + } + return result; +} + +static int sc_compare_u16(uint16_t left, uint16_t right) +{ + if (left == right) return 0; + return left < right ? -1 : 1; +} + +static int sc_compare_u32(uint32_t left, uint32_t right) +{ + if (left == right) return 0; + return left < right ? -1 : 1; +} + +static int sc_entry_compare(const ScPanel *panel, + const ScDirEntry *left, + const ScDirEntry *right) +{ + uint8_t left_group = sc_entry_group(left); + uint8_t right_group = sc_entry_group(right); + int result = 0; + + if (left_group != right_group) { + return left_group < right_group ? -1 : 1; + } + + switch (panel->sort_key) { + case SC_SORT_EXTENSION: + result = sc_compare_text(sc_extension(left->name), + sc_extension(right->name)); + break; + case SC_SORT_SIZE: + result = sc_compare_u32(left->size, right->size); + break; + case SC_SORT_DATE: + result = sc_compare_u16(left->date, right->date); + if (result == 0) { + result = sc_compare_u16(left->time, right->time); + } + break; + case SC_SORT_NAME: + default: + break; + } + if (result == 0) result = sc_compare_text(left->name, right->name); + return panel->sort_descending ? -result : result; +} + +int sc_sort_panel(ScPanel *panel) __banked +{ + uint16_t gap; + uint16_t i; + uint16_t j; + ScDirEntry moving; + ScDirEntry previous; + + for (gap = (uint16_t)(panel->count / 2u); gap != 0u; gap /= 2u) { + for (i = gap; i < panel->count; i++) { + if (sc_store_get(panel, i, &moving) != 0) return -1; + j = i; + while (j >= gap) { + if (sc_store_get(panel, (uint16_t)(j - gap), + &previous) != 0) return -1; + if (sc_entry_compare(panel, &previous, &moving) <= 0) break; + if (sc_store_put(panel, j, &previous) != 0) return -1; + j = (uint16_t)(j - gap); + } + if (sc_store_put(panel, j, &moving) != 0) return -1; + } + } + return 0; +} + +int sc_sort_set(ScPanel *panel, uint8_t key) __banked +{ + ScDirEntry selected; + ScDirEntry current; + uint8_t have_selection = 0u; + uint8_t old_key = panel->sort_key; + uint8_t old_descending = panel->sort_descending; + uint16_t i; + + if (key > SC_SORT_DATE) return -1; + if (panel->count != 0u && + sc_store_get(panel, panel->cursor, &selected) == 0) { + have_selection = 1u; + } + + if (panel->sort_key == key) { + panel->sort_descending ^= 1u; + } else { + panel->sort_key = key; + panel->sort_descending = 0u; + } + + if (sc_sort_panel(panel) != 0) { + panel->sort_key = old_key; + panel->sort_descending = old_descending; + return -1; + } + + if (have_selection) { + for (i = 0u; i < panel->count; i++) { + if (sc_store_get(panel, i, ¤t) != 0) return -1; + if (strcmp(current.name, selected.name) == 0) { + panel->cursor = i; + break; + } + } + } + sc_panel_normalize_view(panel); + panel->dirty = 1u; + return 0; +} diff --git a/applications/Volkov/src/sc_source_fs.c b/applications/Volkov/src/sc_source_fs.c new file mode 100644 index 0000000..1646051 --- /dev/null +++ b/applications/Volkov/src/sc_source_fs.c @@ -0,0 +1,246 @@ +/* + * sc_source_fs.c — чтение каталога DSS в одну EMM-страницу панели. + * + * F_FIRST/F_NEXT используют один общий near-буфер. DSS имеет глобальный CWD, + * поэтому каждый scan/change_dir сохраняет его и восстанавливает до возврата. + */ + +#include +#include +#include +#include +#include +#include "sc_source.h" +#include "sc_sort.h" +#include "sc_store.h" + +#define SC_FIND_ATTRS (FA_RDONLY | FA_HIDDEN | FA_SYSTEM | FA_DIREC | FA_ARCH) + +/* DSS 1.71: каталог больше одной 16-КБ страницы завершается этим + * недокументированным в libc кодом. Это не потеря уже прочитанных данных: + * панель принимает частичный список и показывает признак усечения. */ +#define SC_DSS_TOO_MANY_FILES 35 + +/* Один near-буфер обязателен для всей пары F_FIRST/F_NEXT. Его адрес не + * влияет на системный предел DSS и не должен отнимать хвост банка W1. */ +static ffblk_t sc_source_find; +static char sc_source_process_cwd[256]; +static char sc_source_old_path[256]; +static char sc_source_new_path[256]; + +static void sc_copy_string(char *dst, const char *src, uint16_t capacity) +{ + uint16_t i = 0u; + if (capacity == 0u) return; + while (i + 1u < capacity && src[i] != '\0') { + dst[i] = src[i]; + i++; + } + dst[i] = '\0'; +} + +static uint8_t sc_path_is_root(const char *path) +{ + uint16_t i = 0u; + uint8_t separators = 0u; + + if (path[0] == '\\' && path[1] == '\0') return 1u; + if (path[0] != '\0' && path[1] == ':') i = 2u; + for (; path[i] != '\0'; i++) { + if (path[i] == '\\' || path[i] == '/') separators++; + else return 0u; + } + return separators <= 1u; +} + +static uint8_t sc_is_dot_name(const char *name) +{ + return (name[0] == '.' && name[1] == '\0') || + (name[0] == '.' && name[1] == '.' && name[2] == '\0'); +} + +static int sc_source_add_parent(ScPanel *panel) +{ + ScDirEntry entry; + memset(&entry, 0, sizeof(entry)); + entry.name[0] = '.'; + entry.name[1] = '.'; + entry.attr = FA_DIREC; + entry.flags = SC_ENTRY_PARENT; + if (sc_store_put(panel, panel->count, &entry) != 0) return -1; + panel->count++; + return 0; +} + +static int sc_source_add_found(ScPanel *panel) +{ + ScDirEntry entry; + + if (panel->count >= SC_DIR_CAPACITY) { + panel->truncated = 1u; + return 1; + } + memset(&entry, 0, sizeof(entry)); + sc_copy_string(entry.name, sc_source_find.found_name, + (uint16_t)sizeof(entry.name)); + entry.size = sc_source_find.size; + entry.date = sc_source_find.date; + entry.time = sc_source_find.time; + entry.attr = sc_source_find.found_attr; + if (sc_store_put(panel, panel->count, &entry) != 0) return -1; + panel->count++; + if (!(entry.attr & FA_DIREC)) panel->total_size += entry.size; + return 0; +} + +static int sc_source_add_dss_limit(ScPanel *panel) +{ + ScDirEntry entry; + + /* Предупреждение обязано быть видимым. Если будущий DSS когда-нибудь + * вернёт код 35 уже после заполнения нашей страницы, уступаем маркеру + * последнюю строку и корректируем сумму удалённого файла. */ + if (panel->count >= SC_DIR_CAPACITY) { + if (sc_store_get(panel, (uint16_t)(SC_DIR_CAPACITY - 1u), + &entry) != 0) return -1; + if (!(entry.attr & FA_DIREC)) panel->total_size -= entry.size; + panel->count = (uint16_t)(SC_DIR_CAPACITY - 1u); + } + + memset(&entry, 0, sizeof(entry)); + sc_copy_string(entry.name, ">>> MORE...", (uint16_t)sizeof(entry.name)); + entry.flags = SC_ENTRY_DSS_LIMIT; + if (sc_store_put(panel, panel->count, &entry) != 0) return -1; + panel->count++; + panel->truncated = 1u; + return 0; +} + +static void sc_source_restore_selection(ScPanel *panel, + const char *previous_name) +{ + ScDirEntry entry; + uint16_t i; + + if (previous_name[0] == '\0') { + sc_panel_normalize_view(panel); + return; + } + for (i = 0u; i < panel->count; i++) { + if (sc_store_get(panel, i, &entry) != 0) break; + if (strcmp(entry.name, previous_name) == 0) { + panel->cursor = i; + break; + } + } + sc_panel_normalize_view(panel); +} + +int sc_source_fs_scan(ScPanel *panel) __banked +{ + ScDirEntry selected; + char previous_name[13]; + int rc; + int scan_errno = 0; + uint8_t done = 0u; + uint8_t dss_limited = 0u; + + previous_name[0] = '\0'; + if (panel->count != 0u && + sc_store_get(panel, panel->cursor, &selected) == 0) { + sc_copy_string(previous_name, selected.name, + (uint16_t)sizeof(previous_name)); + } + if (!getcwd(sc_source_process_cwd, + (uint16_t)sizeof(sc_source_process_cwd))) return -1; + if (chdir(panel->path) != 0) return -1; + + sc_store_clear(panel); + if (!sc_path_is_root(panel->path) && sc_source_add_parent(panel) != 0) { + scan_errno = ENOMEM; + done = 1u; + } + + if (!done) { + errno = 0; + /* DSS 1.71/BIOS 3.06 трактует `*` как имя без расширения. Только + * подтверждённый P1-шаблон `*.*` перечисляет полный каталог. */ + rc = ffirst("*.*", &sc_source_find, SC_FIND_ATTRS); + if (rc != 0) { + if (errno == SC_DSS_TOO_MANY_FILES) dss_limited = 1u; + else if (errno != ENOENT) scan_errno = errno; + } else { + for (;;) { + if (!sc_is_dot_name(sc_source_find.found_name) && + !(sc_source_find.found_attr & FA_LABEL)) { + rc = sc_source_add_found(panel); + if (rc < 0) { + scan_errno = ENOMEM; + break; + } + /* 641-я подходящая запись уже доказала усечение. DSS + * хранит итератор глобально, но следующий F_FIRST его + * переинициализирует; дочитывать огромный каталог нельзя. */ + if (rc > 0) break; + } + errno = 0; + if (fnext(&sc_source_find) != 0) { + if (errno == SC_DSS_TOO_MANY_FILES) { + dss_limited = 1u; + } else if (errno != ENOENT) { + scan_errno = errno; + } + break; + } + } + } + } + + if (dss_limited && scan_errno == 0 && + sc_source_add_dss_limit(panel) != 0) scan_errno = ENOMEM; + + if (chdir(sc_source_process_cwd) != 0) return -2; + if (scan_errno != 0) { + errno = scan_errno; + return -1; + } + if (sc_sort_panel(panel) != 0) { + errno = ENOMEM; + return -1; + } + sc_source_restore_selection(panel, previous_name); + panel->dirty = 1u; + return 0; +} + +int sc_source_fs_change_dir(ScPanel *panel, const char *name) __banked +{ + int result; + + sc_copy_string(sc_source_old_path, panel->path, + (uint16_t)sizeof(sc_source_old_path)); + if (!getcwd(sc_source_process_cwd, + (uint16_t)sizeof(sc_source_process_cwd))) return -1; + if (chdir(panel->path) != 0) return -1; + if (chdir(name) != 0) { + (void)chdir(sc_source_process_cwd); + return -1; + } + if (!getcwd(sc_source_new_path, + (uint16_t)sizeof(sc_source_new_path))) { + (void)chdir(sc_source_process_cwd); + return -1; + } + if (chdir(sc_source_process_cwd) != 0) return -2; + + sc_copy_string(panel->path, sc_source_new_path, + (uint16_t)sizeof(panel->path)); + panel->count = 0u; + result = sc_source_fs_scan(panel); + if (result != 0) { + sc_copy_string(panel->path, sc_source_old_path, + (uint16_t)sizeof(panel->path)); + (void)sc_source_fs_scan(panel); + } + return result; +} diff --git a/applications/Volkov/src/sc_store.c b/applications/Volkov/src/sc_store.c new file mode 100644 index 0000000..35ef5ba --- /dev/null +++ b/applications/Volkov/src/sc_store.c @@ -0,0 +1,46 @@ +/* + * sc_store.c — поэлементный доступ к странице каталога через W3. + * + * bank_read/bank_write сами сохраняют и восстанавливают страницу W3. + * Ни одна функция не возвращает указатель внутрь отображённой страницы. + */ + +#include +#include +#include "sc_store.h" + +static uint16_t sc_store_offset(uint16_t index) +{ + return (uint16_t)(index * (uint16_t)sizeof(ScDirEntry)); +} + +int sc_store_get(const ScPanel *panel, uint16_t index, ScDirEntry *out) +{ + if (!panel || !out || index >= panel->count || + index >= SC_DIR_CAPACITY || panel->phys_page == 0u) return -1; + bank_read(panel->phys_page, sc_store_offset(index), out, + (uint16_t)sizeof(*out)); + return 0; +} + +int sc_store_put(const ScPanel *panel, uint16_t index, + const ScDirEntry *entry) +{ + if (!panel || !entry || index >= SC_DIR_CAPACITY || + panel->phys_page == 0u) return -1; + bank_write(panel->phys_page, sc_store_offset(index), entry, + (uint16_t)sizeof(*entry)); + return 0; +} + +void sc_store_clear(ScPanel *panel) +{ + panel->count = 0u; + panel->cursor = 0u; + panel->top = 0u; + panel->total_size = 0ul; + panel->selected_count = 0u; + panel->selected_size = 0ul; + panel->truncated = 0u; + panel->dirty = 1u; +} diff --git a/applications/Volkov/src/sc_theme.c b/applications/Volkov/src/sc_theme.c new file mode 100644 index 0000000..ae0d80a --- /dev/null +++ b/applications/Volkov/src/sc_theme.c @@ -0,0 +1,108 @@ +/* + * sc_theme.c — владение многоцветной текстовой палитрой Commander. + * + * BIOS-функции работают с четырьмя планами: paper, ink, flash-paper и + * flash-ink. Для каждого используемого атрибута исходные RGB сохраняются, + * обе пары задаются одинаково, а при shutdown полностью восстанавливаются. + */ + +#include +#include +#include "sc_theme.h" + +#define SC_THEME_COLOR_COUNT 9u +#define SC_THEME_PLANE_COUNT 4u + +typedef struct { + uint8_t attr; + uint8_t paper_r; + uint8_t paper_g; + uint8_t paper_b; + uint8_t ink_r; + uint8_t ink_g; + uint8_t ink_b; +} ScThemeColor; + +static const ScThemeColor sc_theme_colors[SC_THEME_COLOR_COUNT] = { + { SC_ATTR_FRAME, 0x00, 0x00, 0xA8, 0xFF, 0xFF, 0xFF }, + { SC_ATTR_TITLE, 0x00, 0x00, 0xA8, 0xFF, 0xFF, 0x00 }, + { SC_ATTR_NORMAL, 0x00, 0x00, 0xA8, 0x55, 0xFF, 0xFF }, + { SC_ATTR_FILE, 0x00, 0x00, 0xA8, 0xC0, 0xC0, 0xC0 }, + { SC_ATTR_ACTIVE, 0x00, 0xA8, 0xA8, 0x00, 0x00, 0x00 }, + { SC_ATTR_STATUS, 0xC0, 0xC0, 0xC0, 0x00, 0x00, 0x00 }, + { SC_ATTR_DIALOG, 0xA8, 0x00, 0x00, 0xFF, 0xFF, 0xFF }, + { SC_ATTR_LIMIT, 0x00, 0x00, 0xA8, 0xFF, 0x55, 0x55 }, + { SC_ATTR_LIMIT_ACTIVE, + 0x00, 0xA8, 0xA8, 0xFF, 0x55, 0x55 } +}; + +/* Static-буфер гарантированно находится в W2, как требует BIOS $A4. */ +static uint8_t sc_theme_saved[SC_THEME_COLOR_COUNT] + [SC_THEME_PLANE_COUNT][3u]; +static uint8_t sc_theme_started; + +static void sc_theme_set_plane(uint8_t plane, const ScThemeColor *color) +{ + if (plane == TEXT_PAL_PAPER || plane == TEXT_PAL_BLINK_PAPER) { + text_pal_set_color(plane, color->attr, + color->paper_r, color->paper_g, color->paper_b); + } else { + text_pal_set_color(plane, color->attr, + color->ink_r, color->ink_g, color->ink_b); + } +} + +int sc_theme_init(void) +{ + uint8_t color_index; + uint8_t plane; + + if (sc_theme_started) return 0; + for (color_index = 0u; color_index < SC_THEME_COLOR_COUNT; + color_index++) { + const ScThemeColor *color = &sc_theme_colors[color_index]; + for (plane = 0u; plane < SC_THEME_PLANE_COUNT; plane++) { + text_pal_get_color(plane, color->attr, + &sc_theme_saved[color_index][plane][0], + &sc_theme_saved[color_index][plane][1], + &sc_theme_saved[color_index][plane][2]); + sc_theme_set_plane(plane, color); + } + } + sc_theme_started = 1u; + return 0; +} + +void sc_theme_reapply(void) +{ + uint8_t color_index; + uint8_t plane; + + if (!sc_theme_started) return; + for (color_index = 0u; color_index < SC_THEME_COLOR_COUNT; + color_index++) { + const ScThemeColor *color = &sc_theme_colors[color_index]; + for (plane = 0u; plane < SC_THEME_PLANE_COUNT; plane++) { + sc_theme_set_plane(plane, color); + } + } +} + +void sc_theme_shutdown(void) +{ + uint8_t color_index; + uint8_t plane; + + if (!sc_theme_started) return; + for (color_index = 0u; color_index < SC_THEME_COLOR_COUNT; + color_index++) { + for (plane = 0u; plane < SC_THEME_PLANE_COUNT; plane++) { + text_pal_set_color( + plane, sc_theme_colors[color_index].attr, + sc_theme_saved[color_index][plane][0], + sc_theme_saved[color_index][plane][1], + sc_theme_saved[color_index][plane][2]); + } + } + sc_theme_started = 0u; +} diff --git a/applications/Volkov/src/sc_tree.c b/applications/Volkov/src/sc_tree.c new file mode 100644 index 0000000..0d53fda --- /dev/null +++ b/applications/Volkov/src/sc_tree.c @@ -0,0 +1,432 @@ +/* + * sc_tree.c — пошаговый BFS-обход каталога для рекурсивных F5/F8. + * + * Очередь компонентов хранится в отдельной динамической EMM-странице W3. + * Каждый узел содержит parent-index и DOS 8.3 name; абсолютные пути + * восстанавливаются перед операцией. Первый 256-байтовый диапазон страницы + * хранит исходный CWD процесса, поэтому job всегда может его восстановить. + */ + +#include +#include +#include +#include +#include +#include +#include "sc_path.h" +#include "sc_tree.h" + +#define SC_TREE_SAVED_CWD_SIZE 256u +#define SC_TREE_NODE_OFFSET 256u +#define SC_TREE_NODE_NONE 0xFFFFu +#define SC_TREE_MAX_DEPTH 32u +#define SC_TREE_FIND_ATTRS \ + (FA_RDONLY | FA_HIDDEN | FA_SYSTEM | FA_DIREC | FA_ARCH) +#define SC_TREE_DSS_LIMIT 35 + +typedef struct { + uint16_t parent; + char name[13]; + uint8_t depth; +} ScTreeNode; + +typedef char ScTreeNode_must_be_16_bytes[ + (sizeof(ScTreeNode) == 16u) ? 1 : -1]; + +#define SC_TREE_NODE_CAPACITY \ + ((16384u - SC_TREE_NODE_OFFSET) / (uint16_t)sizeof(ScTreeNode)) + +static ffblk_t sc_tree_find; + +static uint8_t sc_tree_upper(uint8_t ch) +{ + if (ch >= 'a' && ch <= 'z') return (uint8_t)(ch - ('a' - 'A')); + if (ch == '/') return '\\'; + return ch; +} + +/* Истина, если path совпадает с root либо лежит внутри него. */ +static uint8_t sc_tree_path_contains(const char *root, const char *path) +{ + while (*root != '\0' && *path != '\0') { + if (sc_tree_upper((uint8_t)*root) != + sc_tree_upper((uint8_t)*path)) return 0u; + root++; + path++; + } + if (*root != '\0') return 0u; + if (*path == '\0') return 1u; + return (uint8_t)(*path == '\\' || *path == '/'); +} + +static uint16_t sc_tree_node_offset(uint16_t index) +{ + return (uint16_t)(SC_TREE_NODE_OFFSET + + index * (uint16_t)sizeof(ScTreeNode)); +} + +static int sc_tree_get(const ScTreeJob *job, uint16_t index, + ScTreeNode *node) +{ + if (!job || !node || index >= job->tail || + index >= SC_TREE_NODE_CAPACITY || job->page == 0u) { + errno = EINVAL; + return -1; + } + bank_read(job->page, sc_tree_node_offset(index), node, + (uint16_t)sizeof(*node)); + return 0; +} + +static int sc_tree_put(ScTreeJob *job, uint16_t parent, + const char *name, uint8_t depth) +{ + ScTreeNode node; + uint8_t i = 0u; + + if (job->tail >= SC_TREE_NODE_CAPACITY || depth > SC_TREE_MAX_DEPTH) { + errno = depth > SC_TREE_MAX_DEPTH ? EINAME : ENOMEM; + return -1; + } + memset(&node, 0, sizeof(node)); + node.parent = parent; + node.depth = depth; + while (i < 12u && name[i] != '\0') { + node.name[i] = name[i]; + i++; + } + if (name[i] != '\0') { + errno = EINAME; + return -1; + } + bank_write(job->page, sc_tree_node_offset(job->tail), &node, + (uint16_t)sizeof(node)); + job->tail++; + return 0; +} + +static int sc_tree_copy_base(char *out, const char *base) +{ + uint16_t i = 0u; + + while (base[i] != '\0') { + if (i + 1u >= SC_COPY_PATH_CAPACITY) { + errno = EINAME; + return -1; + } + out[i] = base[i]; + i++; + } + out[i] = '\0'; + return 0; +} + +static int sc_tree_append(char *out, const char *name) +{ + uint16_t pos = 0u; + uint16_t i = 0u; + + while (out[pos] != '\0') pos++; + if (pos != 0u && out[pos - 1u] != '\\' && out[pos - 1u] != '/') { + if (pos + 1u >= SC_COPY_PATH_CAPACITY) { + errno = EINAME; + return -1; + } + out[pos++] = '\\'; + } + while (name[i] != '\0') { + if (pos + 1u >= SC_COPY_PATH_CAPACITY) { + errno = EINAME; + return -1; + } + out[pos++] = name[i++]; + } + out[pos] = '\0'; + return 0; +} + +static int sc_tree_build_path(const ScTreeJob *job, uint16_t index, + const char *base, char *out) +{ + uint16_t chain[SC_TREE_MAX_DEPTH]; + uint8_t count = 0u; + ScTreeNode node; + + while (index != SC_TREE_NODE_NONE) { + if (count >= SC_TREE_MAX_DEPTH || sc_tree_get(job, index, &node) != 0) { + errno = EINAME; + return -1; + } + chain[count++] = index; + index = node.parent; + } + if (sc_tree_copy_base(out, base) != 0) return -1; + while (count != 0u) { + if (sc_tree_get(job, chain[--count], &node) != 0 || + sc_tree_append(out, node.name) != 0) return -1; + } + return 0; +} + +static uint8_t sc_tree_is_dot(const char *name) +{ + return (uint8_t)((name[0] == '.' && name[1] == '\0') || + (name[0] == '.' && name[1] == '.' && + name[2] == '\0')); +} + +static void sc_tree_found_entry(ScDirEntry *entry) +{ + memset(entry, 0, sizeof(*entry)); + strncpy(entry->name, sc_tree_find.found_name, + (uint16_t)(sizeof(entry->name) - 1u)); + entry->name[sizeof(entry->name) - 1u] = '\0'; + entry->size = sc_tree_find.size; + entry->date = sc_tree_find.date; + entry->time = sc_tree_find.time; + entry->attr = sc_tree_find.found_attr; +} + +static int sc_tree_fail(ScTreeJob *job, int error) +{ + if (error == 0) error = EUNKERR; + job->error = error; + errno = error; + return SC_TREE_STEP_ERROR; +} + +int sc_tree_begin(ScTreeJob *job, ScCopyJob *copy) __banked +{ + if (!job || !copy) { + errno = EINVAL; + return -1; + } + memset(job, 0, sizeof(*job)); + job->block = mem_alloc_pages(1u); + if (job->block == 0u) return -1; + job->page = mem_get_page(job->block, 0u); + if (job->page == 0u) { + mem_free_block(job->block); + job->block = 0u; + return -1; + } + if (!getcwd(copy->source_path, SC_COPY_PATH_CAPACITY)) { + mem_free_block(job->block); + job->block = 0u; + job->page = 0u; + return -1; + } + bank_write(job->page, 0u, copy->source_path, SC_TREE_SAVED_CWD_SIZE); + job->active = 1u; + return 0; +} + +int sc_tree_add_root(ScTreeJob *job, ScCopyJob *copy, + const char *source_base, const char *target_base, + const char *name) __banked +{ + if (!job || !copy || !job->active || !source_base || !target_base || + !name || name[0] == '\0') { + errno = EINVAL; + return -1; + } + if (sc_path_join(copy->source_path, SC_COPY_PATH_CAPACITY, + source_base, name) != 0) return -1; + if (sc_tree_path_contains(copy->source_path, target_base)) { + errno = EINVAL; + return -1; + } + return sc_tree_put(job, SC_TREE_NODE_NONE, name, 1u); +} + +int sc_tree_add_delete_root(ScTreeJob *job, const char *name) __banked +{ + if (!job || !job->active || !name || name[0] == '\0') { + errno = EINVAL; + return -1; + } + return sc_tree_put(job, SC_TREE_NODE_NONE, name, 1u); +} + +int sc_tree_next(ScTreeJob *job, ScCopyJob *copy, + const char *source_base, const char *target_base, + ScDirEntry *entry) __banked +{ + int rc; + int scan_errno; + ScTreeNode node; + + if (!job || !copy || !entry || !job->active) { + errno = EINVAL; + return SC_TREE_STEP_ERROR; + } + + while (job->head < job->tail) { + job->current = job->head; + if (job->scan_state == 0u) { + if (sc_tree_build_path(job, job->current, source_base, + copy->source_path) != 0) { + return sc_tree_fail(job, errno); + } + if (chdir(copy->source_path) != 0) { + return sc_tree_fail(job, errno); + } + if (sc_tree_build_path(job, job->current, target_base, + copy->target_path) != 0) { + return sc_tree_fail(job, errno); + } + if (mkdir(copy->target_path) != 0) { + return sc_tree_fail(job, errno); + } + job->created_directories++; + job->scan_state = 1u; + return SC_TREE_STEP_DIRECTORY; + } + + errno = 0; + if (job->scan_state == 1u) { + rc = ffirst("*.*", &sc_tree_find, SC_TREE_FIND_ATTRS); + if (rc == 0) job->scan_state = 2u; + } else { + rc = fnext(&sc_tree_find); + } + if (rc != 0) { + scan_errno = errno; + if (scan_errno == ENOENT) { + job->head++; + job->scan_state = 0u; + continue; + } + if (scan_errno == SC_TREE_DSS_LIMIT) { + return sc_tree_fail(job, scan_errno); + } + return sc_tree_fail(job, scan_errno); + } + + if (sc_tree_is_dot(sc_tree_find.found_name) || + (sc_tree_find.found_attr & FA_LABEL)) continue; + if (sc_tree_find.found_attr & FA_DIREC) { + if (sc_tree_get(job, job->current, &node) != 0 || + sc_tree_put(job, job->current, sc_tree_find.found_name, + (uint8_t)(node.depth + 1u)) != 0) { + return sc_tree_fail(job, errno); + } + continue; + } + + sc_tree_found_entry(entry); + if (sc_tree_build_path(job, job->current, target_base, + copy->target_path) != 0 || + sc_tree_append(copy->target_path, entry->name) != 0) { + return sc_tree_fail(job, errno); + } + job->files_seen++; + return SC_TREE_STEP_FILE; + } + return SC_TREE_STEP_DONE; +} + +int sc_tree_delete_next(ScTreeJob *job, ScCopyJob *copy, + const char *source_base, + ScDirEntry *entry) __banked +{ + int rc; + int scan_errno; + ScTreeNode node; + + if (!job || !copy || !source_base || !entry || !job->active) { + errno = EINVAL; + return SC_TREE_STEP_ERROR; + } + + for (;;) { + if (job->scan_state == 3u) { + if (job->reverse == 0u) return SC_TREE_STEP_DONE; + job->reverse--; + if (sc_tree_build_path(job, job->reverse, source_base, + copy->target_path) != 0) { + return sc_tree_fail(job, errno); + } + return SC_TREE_STEP_DIRECTORY; + } + + if (job->head >= job->tail) { + if (chdir(source_base) != 0) return sc_tree_fail(job, errno); + job->reverse = job->tail; + job->scan_state = 3u; + continue; + } + + job->current = job->head; + if (job->scan_state == 0u) { + if (sc_tree_build_path(job, job->current, source_base, + copy->source_path) != 0 || + chdir(copy->source_path) != 0) { + return sc_tree_fail(job, errno); + } + job->scan_state = 1u; + return SC_TREE_STEP_SCAN_DIR; + } + + errno = 0; + if (job->scan_state == 1u) { + rc = ffirst("*.*", &sc_tree_find, SC_TREE_FIND_ATTRS); + if (rc == 0) job->scan_state = 2u; + } else { + rc = fnext(&sc_tree_find); + } + if (rc != 0) { + scan_errno = errno; + if (scan_errno == ENOENT) { + job->head++; + job->scan_state = 0u; + continue; + } + return sc_tree_fail(job, scan_errno); + } + + if (sc_tree_is_dot(sc_tree_find.found_name) || + (sc_tree_find.found_attr & FA_LABEL)) continue; + if (sc_tree_find.found_attr & FA_DIREC) { + if (sc_tree_get(job, job->current, &node) != 0 || + sc_tree_put(job, job->current, sc_tree_find.found_name, + (uint8_t)(node.depth + 1u)) != 0) { + return sc_tree_fail(job, errno); + } + /* Возвращаем управление после каждого enqueue: иначе каталог с + * сотнями подкаталогов надолго задержит проверку Esc. */ + return SC_TREE_STEP_SCAN_DIR; + } + + sc_tree_found_entry(entry); + if (sc_tree_build_path(job, job->current, source_base, + copy->target_path) != 0 || + sc_tree_append(copy->target_path, entry->name) != 0) { + return sc_tree_fail(job, errno); + } + job->files_seen++; + return SC_TREE_STEP_FILE; + } +} + +int sc_tree_finish(ScTreeJob *job, ScCopyJob *copy) __banked +{ + int restore_errno = 0; + + if (!job || !copy) { + errno = EINVAL; + return -1; + } + if (!job->active) return 0; + bank_read(job->page, 0u, copy->source_path, SC_TREE_SAVED_CWD_SIZE); + if (chdir(copy->source_path) != 0) restore_errno = errno; + mem_free_block(job->block); + job->block = 0u; + job->page = 0u; + job->active = 0u; + if (restore_errno != 0) { + errno = restore_errno; + return -1; + } + return 0; +} diff --git a/applications/Volkov/src/sc_video_system.c b/applications/Volkov/src/sc_video_system.c new file mode 100644 index 0000000..dd31f69 --- /dev/null +++ b/applications/Volkov/src/sc_video_system.c @@ -0,0 +1,58 @@ +/* + * sc_video_system.c — полноэкранный системный backend Commander. + * + * Использует только абсолютные ESTEX WINREST/SCROLL. Окна BIOS и прямая + * адресация VRAM не применяются. Палитрой владеет отдельный sc_theme. + */ + +#include +#include +#include "sc_video.h" + +static uint8_t sc_previous_mode; +static uint8_t sc_video_started; + +int sc_video_init(void) +{ + sc_previous_mode = gettextmode(); + if (settextmode(TEXT_MODE_80x32) != 0) return -1; + sc_video_started = 1u; + return 0; +} + +int sc_video_restore(void) +{ + if (!sc_video_started) return -1; + return settextmode(TEXT_MODE_80x32); +} + +void sc_video_shutdown(void) +{ + if (!sc_video_started) return; + + textattr(COLOR(COLOR_WHITE, COLOR_BLACK)); + clrscr(); + if (sc_previous_mode == TEXT_MODE_40x32 || + sc_previous_mode == TEXT_MODE_80x32) { + (void)settextmode(sc_previous_mode); + } + sc_video_started = 0u; +} + +int sc_video_present_rect(uint8_t page, uint16_t offset, + uint8_t row, uint8_t col, + uint8_t height, uint8_t width) +{ + if (height == 0u || width == 0u) return 0; + sc_platform_winrest(row, col, height, width, page, offset); + return 0; +} + +int sc_video_scroll_rect(uint8_t row, uint8_t col, + uint8_t height, uint8_t width, + uint8_t direction) +{ + if (height == 0u || width == 0u) return 0; + scroll(col, row, width, height, direction, 0u); + return 0; +} diff --git a/applications/Volkov/src/sprcmd.c b/applications/Volkov/src/sprcmd.c new file mode 100644 index 0000000..5d4fdfe --- /dev/null +++ b/applications/Volkov/src/sprcmd.c @@ -0,0 +1,23 @@ +/* sprcmd.c — точка входа Sprinter Commander. */ + +#include +#include +#include "sc_app.h" + +static ScApp sc_app; + +int main(void) +{ + int result = sc_app_init(&sc_app); + + if (result != 0) { + cputs("SPRCMD initialization failed.\r\n"); + return (uint8_t)(-result); + } + sc_app_run(&sc_app); + if (sc_app_cleanup(&sc_app) != 0) { + cputs("SPRCMD cleanup did not restore EMM free pages.\r\n"); + return 4; + } + return 0; +} diff --git a/applications/Volkov/tests/check_p10_rename_hdd.sh b/applications/Volkov/tests/check_p10_rename_hdd.sh new file mode 100755 index 0000000..08b08af --- /dev/null +++ b/applications/Volkov/tests/check_p10_rename_hdd.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# Извлекает результат target-пробы и дерево после UI F6. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +IMAGE=${1:-$PROJECT_DIR/build/hdd/p10_rename.chd} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-rename-check.XXXXXX") +RAW="$TEMP_DIR/p10_rename.raw" +MTC="$TEMP_DIR/mtools.conf" +EXTRACTED="$TEMP_DIR/extracted" + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +chdman extractraw -i "$IMAGE" -o "$RAW" -f >/dev/null +printf 'drive z: file="%s" partition=1\n' "$RAW" > "$MTC" +export MTOOLSRC="$MTC" MTOOLS_SKIP_CHECK=1 +mkdir "$EXTRACTED" +mcopy -s z:/RENTEST z:/RENRES.TXT "$EXTRACTED/" +python3 "$PROJECT_DIR/tests/p10_rename_fixture.py" verify "$EXTRACTED" diff --git a/applications/Volkov/tests/check_p11_delete_hdd.sh b/applications/Volkov/tests/check_p11_delete_hdd.sh new file mode 100755 index 0000000..8c4a8b5 --- /dev/null +++ b/applications/Volkov/tests/check_p11_delete_hdd.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# Извлекает результат target-пробы и дерево после UI F8. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +IMAGE=${1:-$PROJECT_DIR/build/hdd/p11_delete.chd} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-delete-check.XXXXXX") +RAW="$TEMP_DIR/p11_delete.raw" +MTC="$TEMP_DIR/mtools.conf" +EXTRACTED="$TEMP_DIR/extracted" + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +chdman extractraw -i "$IMAGE" -o "$RAW" -f >/dev/null +printf 'drive z: file="%s" partition=1\n' "$RAW" > "$MTC" +export MTOOLSRC="$MTC" MTOOLS_SKIP_CHECK=1 +mkdir "$EXTRACTED" +mcopy -s z:/DELTEST z:/DELRES.TXT "$EXTRACTED/" +python3 "$PROJECT_DIR/tests/p11_delete_fixture.py" verify "$EXTRACTED" diff --git a/applications/Volkov/tests/check_p12_group_copy_hdd.sh b/applications/Volkov/tests/check_p12_group_copy_hdd.sh new file mode 100755 index 0000000..d6f8c09 --- /dev/null +++ b/applications/Volkov/tests/check_p12_group_copy_hdd.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# Извлекает обе стороны группового F5 и сравнивает их побайтно. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +IMAGE=${1:-$PROJECT_DIR/build/hdd/p12_group_copy.chd} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-group-copy-check.XXXXXX") +RAW="$TEMP_DIR/p12_group_copy.raw" +MTC="$TEMP_DIR/mtools.conf" +EXTRACTED="$TEMP_DIR/extracted" + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +chdman extractraw -i "$IMAGE" -o "$RAW" -f >/dev/null +printf 'drive z: file="%s" partition=1\n' "$RAW" > "$MTC" +export MTOOLSRC="$MTC" MTOOLS_SKIP_CHECK=1 +mkdir "$EXTRACTED" +mcopy -s z:/GRSRC z:/GRDST "$EXTRACTED/" +python3 "$PROJECT_DIR/tests/p12_group_copy_fixture.py" verify "$EXTRACTED" diff --git a/applications/Volkov/tests/check_p13_group_delete_hdd.sh b/applications/Volkov/tests/check_p13_group_delete_hdd.sh new file mode 100755 index 0000000..5dacd2e --- /dev/null +++ b/applications/Volkov/tests/check_p13_group_delete_hdd.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# Извлекает дерево после группового F8 и проверяет его. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +IMAGE=${1:-$PROJECT_DIR/build/hdd/p13_group_delete.chd} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-group-delete-check.XXXXXX") +RAW="$TEMP_DIR/p13_group_delete.raw" +MTC="$TEMP_DIR/mtools.conf" +EXTRACTED="$TEMP_DIR/extracted" + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +chdman extractraw -i "$IMAGE" -o "$RAW" -f >/dev/null +printf 'drive z: file="%s" partition=1\n' "$RAW" > "$MTC" +export MTOOLSRC="$MTC" MTOOLS_SKIP_CHECK=1 +mkdir "$EXTRACTED" +mcopy -s z:/GDEL "$EXTRACTED/" +python3 "$PROJECT_DIR/tests/p13_group_delete_fixture.py" verify "$EXTRACTED" diff --git a/applications/Volkov/tests/check_p14_default_colors_hdd.sh b/applications/Volkov/tests/check_p14_default_colors_hdd.sh new file mode 100755 index 0000000..f6e5fc8 --- /dev/null +++ b/applications/Volkov/tests/check_p14_default_colors_hdd.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# Проверяет неизменность HDD-fixture после визуального теста цветов. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +IMAGE=${1:-$PROJECT_DIR/build/hdd/p14_default_colors.chd} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-default-colors-check.XXXXXX") +RAW="$TEMP_DIR/p14_default_colors.raw" +MTC="$TEMP_DIR/mtools.conf" +EXTRACTED="$TEMP_DIR/extracted" + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +chdman extractraw -i "$IMAGE" -o "$RAW" -f >/dev/null +printf 'drive z: file="%s" partition=1\n' "$RAW" > "$MTC" +export MTOOLSRC="$MTC" MTOOLS_SKIP_CHECK=1 +mkdir "$EXTRACTED" +mcopy -s z:/COLORS "$EXTRACTED/" +python3 "$PROJECT_DIR/tests/p14_default_colors_fixture.py" verify "$EXTRACTED" diff --git a/applications/Volkov/tests/check_p15_tree_cancel_hdd.sh b/applications/Volkov/tests/check_p15_tree_cancel_hdd.sh new file mode 100755 index 0000000..b63961f --- /dev/null +++ b/applications/Volkov/tests/check_p15_tree_cancel_hdd.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# Проверяет носитель после отмены рекурсивного F5. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +IMAGE=${1:-$PROJECT_DIR/build/hdd/p15_tree_cancel.chd} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-tree-cancel-check.XXXXXX") +RAW="$TEMP_DIR/p15_tree_cancel.raw" +MTC="$TEMP_DIR/mtools.conf" +EXTRACTED="$TEMP_DIR/extracted" + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +chdman extractraw -i "$IMAGE" -o "$RAW" -f >/dev/null +printf 'drive z: file="%s" partition=1\n' "$RAW" > "$MTC" +export MTOOLSRC="$MTC" MTOOLS_SKIP_CHECK=1 +mkdir "$EXTRACTED" +mcopy -s z:/CSRC z:/CDST "$EXTRACTED/" +python3 "$PROJECT_DIR/tests/p15_tree_cancel_fixture.py" verify "$EXTRACTED" diff --git a/applications/Volkov/tests/check_p15_tree_copy_hdd.sh b/applications/Volkov/tests/check_p15_tree_copy_hdd.sh new file mode 100755 index 0000000..9257426 --- /dev/null +++ b/applications/Volkov/tests/check_p15_tree_copy_hdd.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# Извлекает дерево после рекурсивного F5 и сравнивает его побайтно. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +IMAGE=${1:-$PROJECT_DIR/build/hdd/p15_tree_copy.chd} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-tree-copy-check.XXXXXX") +RAW="$TEMP_DIR/p15_tree_copy.raw" +MTC="$TEMP_DIR/mtools.conf" +EXTRACTED="$TEMP_DIR/extracted" + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +chdman extractraw -i "$IMAGE" -o "$RAW" -f >/dev/null +printf 'drive z: file="%s" partition=1\n' "$RAW" > "$MTC" +export MTOOLSRC="$MTC" MTOOLS_SKIP_CHECK=1 +mkdir "$EXTRACTED" +mcopy -s z:/SRC z:/DST "$EXTRACTED/" +python3 "$PROJECT_DIR/tests/p15_tree_copy_fixture.py" verify "$EXTRACTED" diff --git a/applications/Volkov/tests/check_p15_tree_self_hdd.sh b/applications/Volkov/tests/check_p15_tree_self_hdd.sh new file mode 100755 index 0000000..2fffc24 --- /dev/null +++ b/applications/Volkov/tests/check_p15_tree_self_hdd.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# Проверяет неизменность source после self-target guard. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +IMAGE=${1:-$PROJECT_DIR/build/hdd/p15_tree_self.chd} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-tree-self-check.XXXXXX") +RAW="$TEMP_DIR/p15_tree_self.raw" +MTC="$TEMP_DIR/mtools.conf" +EXTRACTED="$TEMP_DIR/extracted" + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +chdman extractraw -i "$IMAGE" -o "$RAW" -f >/dev/null +printf 'drive z: file="%s" partition=1\n' "$RAW" > "$MTC" +export MTOOLSRC="$MTC" MTOOLS_SKIP_CHECK=1 +mkdir "$EXTRACTED" +mcopy -s z:/SRC "$EXTRACTED/" +python3 "$PROJECT_DIR/tests/p15_tree_self_fixture.py" verify "$EXTRACTED" diff --git a/applications/Volkov/tests/check_p16_group_tree_copy_hdd.sh b/applications/Volkov/tests/check_p16_group_tree_copy_hdd.sh new file mode 100755 index 0000000..2602ae1 --- /dev/null +++ b/applications/Volkov/tests/check_p16_group_tree_copy_hdd.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# Извлекает и проверяет результат group F5 с directory roots. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +IMAGE=${1:-$PROJECT_DIR/build/hdd/p16_group_tree_copy.chd} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-group-tree-check.XXXXXX") +RAW="$TEMP_DIR/p16_group_tree_copy.raw" +MTC="$TEMP_DIR/mtools.conf" +EXTRACTED="$TEMP_DIR/extracted" + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +chdman extractraw -i "$IMAGE" -o "$RAW" -f >/dev/null +printf 'drive z: file="%s" partition=1\n' "$RAW" > "$MTC" +export MTOOLSRC="$MTC" MTOOLS_SKIP_CHECK=1 +mkdir "$EXTRACTED" +mcopy -s z:/GSRC z:/GDST "$EXTRACTED/" +python3 "$PROJECT_DIR/tests/p16_group_tree_copy_fixture.py" verify "$EXTRACTED" diff --git a/applications/Volkov/tests/check_p17_tree_delete_cancel_hdd.sh b/applications/Volkov/tests/check_p17_tree_delete_cancel_hdd.sh new file mode 100755 index 0000000..989a772 --- /dev/null +++ b/applications/Volkov/tests/check_p17_tree_delete_cancel_hdd.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# Извлекает и проверяет состояние после runtime-cancel рекурсивного F8. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +IMAGE=${1:-$PROJECT_DIR/build/hdd/p17_tree_delete_cancel.chd} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-tree-delete-cancel-check.XXXXXX") +RAW="$TEMP_DIR/p17_tree_delete_cancel.raw" +MTC="$TEMP_DIR/mtools.conf" +EXTRACTED="$TEMP_DIR/extracted" + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +chdman extractraw -i "$IMAGE" -o "$RAW" -f >/dev/null +printf 'drive z: file="%s" partition=1\n' "$RAW" > "$MTC" +export MTOOLSRC="$MTC" MTOOLS_SKIP_CHECK=1 +mkdir "$EXTRACTED" +mcopy -s z:/CDEL "$EXTRACTED/" +python3 "$PROJECT_DIR/tests/p17_tree_delete_cancel_fixture.py" verify "$EXTRACTED" diff --git a/applications/Volkov/tests/check_p17_tree_delete_hdd.sh b/applications/Volkov/tests/check_p17_tree_delete_hdd.sh new file mode 100755 index 0000000..04bcc2c --- /dev/null +++ b/applications/Volkov/tests/check_p17_tree_delete_hdd.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# Извлекает и проверяет результат рекурсивного группового F8. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +IMAGE=${1:-$PROJECT_DIR/build/hdd/p17_tree_delete.chd} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-tree-delete-check.XXXXXX") +RAW="$TEMP_DIR/p17_tree_delete.raw" +MTC="$TEMP_DIR/mtools.conf" +EXTRACTED="$TEMP_DIR/extracted" + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +chdman extractraw -i "$IMAGE" -o "$RAW" -f >/dev/null +printf 'drive z: file="%s" partition=1\n' "$RAW" > "$MTC" +export MTOOLSRC="$MTC" MTOOLS_SKIP_CHECK=1 +mkdir "$EXTRACTED" +mcopy -s z:/RDEL "$EXTRACTED/" +python3 "$PROJECT_DIR/tests/p17_tree_delete_fixture.py" verify "$EXTRACTED" diff --git a/applications/Volkov/tests/check_p18_metadata_hdd.sh b/applications/Volkov/tests/check_p18_metadata_hdd.sh new file mode 100755 index 0000000..8b4ad31 --- /dev/null +++ b/applications/Volkov/tests/check_p18_metadata_hdd.sh @@ -0,0 +1,18 @@ +#!/bin/sh +# Извлекает raw FAT16 и сверяет payload, date/time и атрибуты source/target. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +IMAGE=${1:-$PROJECT_DIR/build/hdd/p18_metadata.chd} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-metadata-check.XXXXXX") +RAW="$TEMP_DIR/p18_metadata.raw" + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +chdman extractraw -i "$IMAGE" -o "$RAW" -f >/dev/null +python3 "$PROJECT_DIR/tests/p18_metadata_fixture.py" verify "$RAW" diff --git a/applications/Volkov/tests/check_p19_overwrite_hdd.sh b/applications/Volkov/tests/check_p19_overwrite_hdd.sh new file mode 100755 index 0000000..773da35 --- /dev/null +++ b/applications/Volkov/tests/check_p19_overwrite_hdd.sh @@ -0,0 +1,23 @@ +#!/bin/sh +# Извлекает результат target-пробы P19 и сверяет все файлы побайтно. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +IMAGE=${1:-$PROJECT_DIR/build/hdd/p19_overwrite.chd} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-p19-check.XXXXXX") +RAW="$TEMP_DIR/p19.raw" +MTC="$TEMP_DIR/mtools.conf" + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +chdman extractraw -i "$IMAGE" -o "$RAW" -f >/dev/null +printf 'drive z: file="%s" partition=1\n' "$RAW" > "$MTC" +export MTOOLSRC="$MTC" MTOOLS_SKIP_CHECK=1 +mkdir -p "$TEMP_DIR/out" +mcopy -s z:/* "$TEMP_DIR/out" +python3 "$PROJECT_DIR/tests/p19_overwrite_fixture.py" verify "$TEMP_DIR/out" diff --git a/applications/Volkov/tests/check_p19_policy_hdd.sh b/applications/Volkov/tests/check_p19_policy_hdd.sh new file mode 100755 index 0000000..46e2b87 --- /dev/null +++ b/applications/Volkov/tests/check_p19_policy_hdd.sh @@ -0,0 +1,23 @@ +#!/bin/sh +# Извлекает UI-результат P19 и сверяет policy-последствия побайтно. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +IMAGE=${1:-$PROJECT_DIR/build/hdd/p19_policy.chd} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-p19-ui-check.XXXXXX") +RAW="$TEMP_DIR/p19.raw" +MTC="$TEMP_DIR/mtools.conf" + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +chdman extractraw -i "$IMAGE" -o "$RAW" -f >/dev/null +printf 'drive z: file="%s" partition=1\n' "$RAW" > "$MTC" +export MTOOLSRC="$MTC" MTOOLS_SKIP_CHECK=1 +mkdir -p "$TEMP_DIR/out" +mcopy -s z:/* "$TEMP_DIR/out" +python3 "$PROJECT_DIR/tests/p19_policy_fixture.py" verify "$TEMP_DIR/out" diff --git a/applications/Volkov/tests/check_p4_copy_hdd.sh b/applications/Volkov/tests/check_p4_copy_hdd.sh new file mode 100755 index 0000000..433571d --- /dev/null +++ b/applications/Volkov/tests/check_p4_copy_hdd.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# Проверяет изменённый MAME-образ после tests/mame_p4_copy_hdd.lua. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +IMAGE=${1:-$PROJECT_DIR/build/hdd/p4_copy.chd} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-copy-check.XXXXXX") +RAW="$TEMP_DIR/p4_copy.raw" +MTC="$TEMP_DIR/mtools.conf" +EXTRACTED="$TEMP_DIR/extracted" + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +chdman extractraw -i "$IMAGE" -o "$RAW" -f >/dev/null +printf 'drive z: file="%s" partition=1\n' "$RAW" > "$MTC" +export MTOOLSRC="$MTC" MTOOLS_SKIP_CHECK=1 +mkdir "$EXTRACTED" +mcopy -s z:/COPYFROM z:/COPYTO "$EXTRACTED/" +python3 "$PROJECT_DIR/tests/p4_fixture.py" verify "$EXTRACTED" diff --git a/applications/Volkov/tests/check_p4_exec_hdd.sh b/applications/Volkov/tests/check_p4_exec_hdd.sh new file mode 100755 index 0000000..0651d97 --- /dev/null +++ b/applications/Volkov/tests/check_p4_exec_hdd.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# Извлекает изменённый HDD и проверяет результаты P4CHILD. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +IMAGE=${1:-$PROJECT_DIR/build/hdd/p4_exec.chd} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-exec-check.XXXXXX") +RAW="$TEMP_DIR/p4_exec.raw" +MTC="$TEMP_DIR/mtools.conf" +EXTRACTED="$TEMP_DIR/extracted" + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +chdman extractraw -i "$IMAGE" -o "$RAW" -f >/dev/null +printf 'drive z: file="%s" partition=1\n' "$RAW" > "$MTC" +export MTOOLSRC="$MTC" MTOOLS_SKIP_CHECK=1 +mkdir "$EXTRACTED" +mcopy -s z:/EXECLEFT z:/EXECRGHT "$EXTRACTED/" +python3 "$PROJECT_DIR/tests/p4_exec_verify.py" "$EXTRACTED" diff --git a/applications/Volkov/tests/check_p5_copy_faults_hdd.sh b/applications/Volkov/tests/check_p5_copy_faults_hdd.sh new file mode 100755 index 0000000..8f3a39d --- /dev/null +++ b/applications/Volkov/tests/check_p5_copy_faults_hdd.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# Извлекает результат низкоуровневой матрицы copy-job из CHD. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +IMAGE=${1:-$PROJECT_DIR/build/hdd/p5_copy_faults.chd} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-copy-faults-check.XXXXXX") +RAW="$TEMP_DIR/p5_copy_faults.raw" +MTC="$TEMP_DIR/mtools.conf" +EXTRACTED="$TEMP_DIR/extracted" + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +chdman extractraw -i "$IMAGE" -o "$RAW" -f >/dev/null +printf 'drive z: file="%s" partition=1\n' "$RAW" > "$MTC" +export MTOOLSRC="$MTC" MTOOLS_SKIP_CHECK=1 +mkdir "$EXTRACTED" +mcopy -s z:/SOURCE z:/TARGET z:/RESULT.TXT "$EXTRACTED/" +python3 "$PROJECT_DIR/tests/p5_copy_faults_fixture.py" verify "$EXTRACTED" diff --git a/applications/Volkov/tests/check_p5_enospc_hdd.sh b/applications/Volkov/tests/check_p5_enospc_hdd.sh new file mode 100755 index 0000000..bc38a5e --- /dev/null +++ b/applications/Volkov/tests/check_p5_enospc_hdd.sh @@ -0,0 +1,25 @@ +#!/bin/sh +# Проверяет cleanup почти полного HDD после ошибки копирования. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +IMAGE=${1:-$PROJECT_DIR/build/hdd/p5_enospc.chd} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-enospc-check.XXXXXX") +RAW="$TEMP_DIR/enospc.raw" +MTC="$TEMP_DIR/mtools.conf" +EXTRACTED="$TEMP_DIR/extracted" + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +chdman extractraw -i "$IMAGE" -o "$RAW" -f >/dev/null +printf 'drive z: file="%s" partition=1\n' "$RAW" > "$MTC" +export MTOOLSRC="$MTC" MTOOLS_SKIP_CHECK=1 +mkdir "$EXTRACTED" +mcopy -s z:/SOURCE z:/TARGET "$EXTRACTED/" +mcopy z:/FILLER.BIN "$EXTRACTED/" +python3 "$PROJECT_DIR/tests/p5_enospc_fixture.py" verify "$EXTRACTED" diff --git a/applications/Volkov/tests/check_p5_hdd.sh b/applications/Volkov/tests/check_p5_hdd.sh new file mode 100755 index 0000000..2a94601 --- /dev/null +++ b/applications/Volkov/tests/check_p5_hdd.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# Извлекает HDD после P5 и сравнивает все двадцать копий. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +IMAGE=${1:-$PROJECT_DIR/build/hdd/p5_stability.chd} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-p5-check.XXXXXX") +RAW="$TEMP_DIR/p5.raw" +MTC="$TEMP_DIR/mtools.conf" +EXTRACTED="$TEMP_DIR/extracted" + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +chdman extractraw -i "$IMAGE" -o "$RAW" -f >/dev/null +printf 'drive z: file="%s" partition=1\n' "$RAW" > "$MTC" +export MTOOLSRC="$MTC" MTOOLS_SKIP_CHECK=1 +mkdir "$EXTRACTED" +mcopy -s z:/SOURCE z:/TARGET "$EXTRACTED/" +python3 "$PROJECT_DIR/tests/p5_fixture.py" verify "$EXTRACTED" diff --git a/applications/Volkov/tests/check_p5_media_hdd.sh b/applications/Volkov/tests/check_p5_media_hdd.sh new file mode 100755 index 0000000..6182d19 --- /dev/null +++ b/applications/Volkov/tests/check_p5_media_hdd.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# Проверяет HDD после unload/load и повторного F5. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +IMAGE=${1:-$PROJECT_DIR/build/hdd/p5_media.chd} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-media-check.XXXXXX") +RAW="$TEMP_DIR/p5_media.raw" +MTC="$TEMP_DIR/mtools.conf" +EXTRACTED="$TEMP_DIR/extracted" + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +chdman extractraw -i "$IMAGE" -o "$RAW" -f >/dev/null +printf 'drive z: file="%s" partition=1\n' "$RAW" > "$MTC" +export MTOOLSRC="$MTC" MTOOLS_SKIP_CHECK=1 +mkdir "$EXTRACTED" +mcopy -s z:/MEDIA z:/DATA.BIN "$EXTRACTED/" +python3 "$PROJECT_DIR/tests/p5_media_fixture.py" verify "$EXTRACTED" diff --git a/applications/Volkov/tests/check_p6_sort_hdd.sh b/applications/Volkov/tests/check_p6_sort_hdd.sh new file mode 100755 index 0000000..4236ba8 --- /dev/null +++ b/applications/Volkov/tests/check_p6_sort_hdd.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# Извлекает SORTRES.TXT и проверяет target-регрессию сортировки. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +IMAGE=${1:-$PROJECT_DIR/build/hdd/p6_sort.chd} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-sort-check.XXXXXX") +RAW="$TEMP_DIR/p6_sort.raw" +MTC="$TEMP_DIR/mtools.conf" +EXTRACTED="$TEMP_DIR/extracted" + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +chdman extractraw -i "$IMAGE" -o "$RAW" -f >/dev/null +printf 'drive z: file="%s" partition=1\n' "$RAW" > "$MTC" +export MTOOLSRC="$MTC" MTOOLS_SKIP_CHECK=1 +mkdir "$EXTRACTED" +mcopy z:/SORTRES.TXT "$EXTRACTED/" +python3 "$PROJECT_DIR/tests/p6_sort_fixture.py" verify "$EXTRACTED" diff --git a/applications/Volkov/tests/check_p7_select_hdd.sh b/applications/Volkov/tests/check_p7_select_hdd.sh new file mode 100755 index 0000000..57b92a1 --- /dev/null +++ b/applications/Volkov/tests/check_p7_select_hdd.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# Извлекает SELRES.TXT и проверяет target-регрессию выбора. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +IMAGE=${1:-$PROJECT_DIR/build/hdd/p7_select.chd} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-select-check.XXXXXX") +RAW="$TEMP_DIR/p7_select.raw" +MTC="$TEMP_DIR/mtools.conf" +EXTRACTED="$TEMP_DIR/extracted" + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +chdman extractraw -i "$IMAGE" -o "$RAW" -f >/dev/null +printf 'drive z: file="%s" partition=1\n' "$RAW" > "$MTC" +export MTOOLSRC="$MTC" MTOOLS_SKIP_CHECK=1 +mkdir "$EXTRACTED" +mcopy z:/SELRES.TXT "$EXTRACTED/" +python3 "$PROJECT_DIR/tests/p7_select_fixture.py" verify "$EXTRACTED" diff --git a/applications/Volkov/tests/check_p9_mkdir_hdd.sh b/applications/Volkov/tests/check_p9_mkdir_hdd.sh new file mode 100755 index 0000000..739b79d --- /dev/null +++ b/applications/Volkov/tests/check_p9_mkdir_hdd.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# Извлекает результат target-пробы и дерево после F7. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +IMAGE=${1:-$PROJECT_DIR/build/hdd/p9_mkdir.chd} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-mkdir-check.XXXXXX") +RAW="$TEMP_DIR/p9_mkdir.raw" +MTC="$TEMP_DIR/mtools.conf" +EXTRACTED="$TEMP_DIR/extracted" + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +chdman extractraw -i "$IMAGE" -o "$RAW" -f >/dev/null +printf 'drive z: file="%s" partition=1\n' "$RAW" > "$MTC" +export MTOOLSRC="$MTC" MTOOLS_SKIP_CHECK=1 +mkdir "$EXTRACTED" +mcopy -s z:/MKTEST z:/MKRES.TXT "$EXTRACTED/" +python3 "$PROJECT_DIR/tests/p9_mkdir_fixture.py" verify "$EXTRACTED" diff --git a/applications/Volkov/tests/make_p10_rename_hdd.sh b/applications/Volkov/tests/make_p10_rename_hdd.sh new file mode 100755 index 0000000..609dc76 --- /dev/null +++ b/applications/Volkov/tests/make_p10_rename_hdd.sh @@ -0,0 +1,21 @@ +#!/bin/sh +# Создаёт HDD с target-пробой и UI-fixture F6 local rename. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +IMAGE=${1:-$PROJECT_DIR/build/hdd/p10_rename.chd} +COMMANDER=${2:-$PROJECT_DIR/build/sprcmd.exe} +PROBE=${3:-$PROJECT_DIR/build/tests/renrt.exe} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-rename.XXXXXX") + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +python3 "$PROJECT_DIR/tests/p10_rename_fixture.py" create "$TEMP_DIR" +sh "$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \ + "$COMMANDER" "$PROBE" "$TEMP_DIR/RENTEST" +printf 'P10 rename HDD ready: %s\n' "$IMAGE" diff --git a/applications/Volkov/tests/make_p11_delete_hdd.sh b/applications/Volkov/tests/make_p11_delete_hdd.sh new file mode 100755 index 0000000..4668bc4 --- /dev/null +++ b/applications/Volkov/tests/make_p11_delete_hdd.sh @@ -0,0 +1,21 @@ +#!/bin/sh +# Создаёт HDD с target-пробой и UI-fixture F8 delete. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +IMAGE=${1:-$PROJECT_DIR/build/hdd/p11_delete.chd} +COMMANDER=${2:-$PROJECT_DIR/build/sprcmd.exe} +PROBE=${3:-$PROJECT_DIR/build/tests/delrt.exe} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-delete.XXXXXX") + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +python3 "$PROJECT_DIR/tests/p11_delete_fixture.py" create "$TEMP_DIR" +"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \ + "$COMMANDER" "$PROBE" "$TEMP_DIR/DELTEST" +printf 'P11 delete HDD ready: %s\n' "$IMAGE" diff --git a/applications/Volkov/tests/make_p12_group_copy_hdd.sh b/applications/Volkov/tests/make_p12_group_copy_hdd.sh new file mode 100755 index 0000000..9be049f --- /dev/null +++ b/applications/Volkov/tests/make_p12_group_copy_hdd.sh @@ -0,0 +1,20 @@ +#!/bin/sh +# Создаёт HDD с двумя панелями для группового F5. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +IMAGE=${1:-$PROJECT_DIR/build/hdd/p12_group_copy.chd} +COMMANDER=${2:-$PROJECT_DIR/build/sprcmd.exe} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-group-copy.XXXXXX") + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +python3 "$PROJECT_DIR/tests/p12_group_copy_fixture.py" create "$TEMP_DIR" +"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \ + "$COMMANDER" "$TEMP_DIR/GRSRC" "$TEMP_DIR/GRDST" +printf 'P12 group-copy HDD ready: %s\n' "$IMAGE" diff --git a/applications/Volkov/tests/make_p13_group_delete_hdd.sh b/applications/Volkov/tests/make_p13_group_delete_hdd.sh new file mode 100755 index 0000000..bf6fda3 --- /dev/null +++ b/applications/Volkov/tests/make_p13_group_delete_hdd.sh @@ -0,0 +1,20 @@ +#!/bin/sh +# Создаёт HDD-fixture группового F8. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +IMAGE=${1:-$PROJECT_DIR/build/hdd/p13_group_delete.chd} +COMMANDER=${2:-$PROJECT_DIR/build/sprcmd.exe} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-group-delete.XXXXXX") + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +python3 "$PROJECT_DIR/tests/p13_group_delete_fixture.py" create "$TEMP_DIR" +"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \ + "$COMMANDER" "$TEMP_DIR/GDEL" +printf 'P13 group-delete HDD ready: %s\n' "$IMAGE" diff --git a/applications/Volkov/tests/make_p14_default_colors_hdd.sh b/applications/Volkov/tests/make_p14_default_colors_hdd.sh new file mode 100755 index 0000000..a44cbd0 --- /dev/null +++ b/applications/Volkov/tests/make_p14_default_colors_hdd.sh @@ -0,0 +1,20 @@ +#!/bin/sh +# Создаёт HDD-fixture стандартных цветов имён. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +IMAGE=${1:-$PROJECT_DIR/build/hdd/p14_default_colors.chd} +COMMANDER=${2:-$PROJECT_DIR/build/sprcmd.exe} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-default-colors.XXXXXX") + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +python3 "$PROJECT_DIR/tests/p14_default_colors_fixture.py" create "$TEMP_DIR" +"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \ + "$COMMANDER" "$TEMP_DIR/COLORS" +printf 'P14 default-colours HDD ready: %s\n' "$IMAGE" diff --git a/applications/Volkov/tests/make_p15_tree_cancel_hdd.sh b/applications/Volkov/tests/make_p15_tree_cancel_hdd.sh new file mode 100755 index 0000000..f997adb --- /dev/null +++ b/applications/Volkov/tests/make_p15_tree_cancel_hdd.sh @@ -0,0 +1,20 @@ +#!/bin/sh +# Создаёт HDD-fixture отмены рекурсивного F5. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +IMAGE=${1:-$PROJECT_DIR/build/hdd/p15_tree_cancel.chd} +COMMANDER=${2:-$PROJECT_DIR/build/sprcmd.exe} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-tree-cancel.XXXXXX") + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +python3 "$PROJECT_DIR/tests/p15_tree_cancel_fixture.py" create "$TEMP_DIR" +"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \ + "$COMMANDER" "$TEMP_DIR/CSRC" "$TEMP_DIR/CDST" +printf 'P15 tree-cancel HDD ready: %s\n' "$IMAGE" diff --git a/applications/Volkov/tests/make_p15_tree_copy_hdd.sh b/applications/Volkov/tests/make_p15_tree_copy_hdd.sh new file mode 100755 index 0000000..be221e9 --- /dev/null +++ b/applications/Volkov/tests/make_p15_tree_copy_hdd.sh @@ -0,0 +1,20 @@ +#!/bin/sh +# Создаёт HDD-fixture рекурсивного F5 одного каталога. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +IMAGE=${1:-$PROJECT_DIR/build/hdd/p15_tree_copy.chd} +COMMANDER=${2:-$PROJECT_DIR/build/sprcmd.exe} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-tree-copy.XXXXXX") + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +python3 "$PROJECT_DIR/tests/p15_tree_copy_fixture.py" create "$TEMP_DIR" +"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \ + "$COMMANDER" "$TEMP_DIR/SRC" "$TEMP_DIR/DST" +printf 'P15 tree-copy HDD ready: %s\n' "$IMAGE" diff --git a/applications/Volkov/tests/make_p15_tree_self_hdd.sh b/applications/Volkov/tests/make_p15_tree_self_hdd.sh new file mode 100755 index 0000000..eac8626 --- /dev/null +++ b/applications/Volkov/tests/make_p15_tree_self_hdd.sh @@ -0,0 +1,20 @@ +#!/bin/sh +# Создаёт HDD-fixture target-inside-source guard. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +IMAGE=${1:-$PROJECT_DIR/build/hdd/p15_tree_self.chd} +COMMANDER=${2:-$PROJECT_DIR/build/sprcmd.exe} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-tree-self.XXXXXX") + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +python3 "$PROJECT_DIR/tests/p15_tree_self_fixture.py" create "$TEMP_DIR" +"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \ + "$COMMANDER" "$TEMP_DIR/SRC" +printf 'P15 tree-self HDD ready: %s\n' "$IMAGE" diff --git a/applications/Volkov/tests/make_p16_group_tree_copy_hdd.sh b/applications/Volkov/tests/make_p16_group_tree_copy_hdd.sh new file mode 100755 index 0000000..7b37dc5 --- /dev/null +++ b/applications/Volkov/tests/make_p16_group_tree_copy_hdd.sh @@ -0,0 +1,20 @@ +#!/bin/sh +# Создаёт HDD-fixture group F5 с двумя directory roots. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +IMAGE=${1:-$PROJECT_DIR/build/hdd/p16_group_tree_copy.chd} +COMMANDER=${2:-$PROJECT_DIR/build/sprcmd.exe} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-group-tree.XXXXXX") + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +python3 "$PROJECT_DIR/tests/p16_group_tree_copy_fixture.py" create "$TEMP_DIR" +"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \ + "$COMMANDER" "$TEMP_DIR/GSRC" "$TEMP_DIR/GDST" +printf 'P16 group-tree HDD ready: %s\n' "$IMAGE" diff --git a/applications/Volkov/tests/make_p17_tree_delete_cancel_hdd.sh b/applications/Volkov/tests/make_p17_tree_delete_cancel_hdd.sh new file mode 100755 index 0000000..e35acc7 --- /dev/null +++ b/applications/Volkov/tests/make_p17_tree_delete_cancel_hdd.sh @@ -0,0 +1,20 @@ +#!/bin/sh +# Создаёт HDD-fixture runtime-cancel рекурсивного группового F8. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +IMAGE=${1:-$PROJECT_DIR/build/hdd/p17_tree_delete_cancel.chd} +COMMANDER=${2:-$PROJECT_DIR/build/sprcmd.exe} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-tree-delete-cancel.XXXXXX") + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +python3 "$PROJECT_DIR/tests/p17_tree_delete_cancel_fixture.py" create "$TEMP_DIR" +"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \ + "$COMMANDER" "$TEMP_DIR/CDEL" +printf 'P17 tree-delete cancel HDD ready: %s\n' "$IMAGE" diff --git a/applications/Volkov/tests/make_p17_tree_delete_hdd.sh b/applications/Volkov/tests/make_p17_tree_delete_hdd.sh new file mode 100755 index 0000000..6b4eb4b --- /dev/null +++ b/applications/Volkov/tests/make_p17_tree_delete_hdd.sh @@ -0,0 +1,20 @@ +#!/bin/sh +# Создаёт HDD-fixture рекурсивного группового F8. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +IMAGE=${1:-$PROJECT_DIR/build/hdd/p17_tree_delete.chd} +COMMANDER=${2:-$PROJECT_DIR/build/sprcmd.exe} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-tree-delete.XXXXXX") + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +python3 "$PROJECT_DIR/tests/p17_tree_delete_fixture.py" create "$TEMP_DIR" +"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \ + "$COMMANDER" "$TEMP_DIR/RDEL" +printf 'P17 tree-delete HDD ready: %s\n' "$IMAGE" diff --git a/applications/Volkov/tests/make_p18_metadata_hdd.sh b/applications/Volkov/tests/make_p18_metadata_hdd.sh new file mode 100755 index 0000000..5e1bdce --- /dev/null +++ b/applications/Volkov/tests/make_p18_metadata_hdd.sh @@ -0,0 +1,36 @@ +#!/bin/sh +# Создаёт HDD с точными FAT date/time и R/H/S/A у source-файла. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +IMAGE=${1:-$PROJECT_DIR/build/hdd/p18_metadata.chd} +COMMANDER=${2:-$PROJECT_DIR/build/sprcmd.exe} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-metadata.XXXXXX") +BASE_CHD="$TEMP_DIR/base.chd" +RAW="$TEMP_DIR/p18_metadata.raw" +MTC="$TEMP_DIR/mtools.conf" + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +python3 "$PROJECT_DIR/tests/p18_metadata_fixture.py" create "$TEMP_DIR" +"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$BASE_CHD" \ + "$COMMANDER" "$TEMP_DIR/MSRC" "$TEMP_DIR/MDST" + +chdman extractraw -i "$BASE_CHD" -o "$RAW" -f >/dev/null +printf 'drive z: file="%s" partition=1\n' "$RAW" > "$MTC" +export MTOOLSRC="$MTC" MTOOLS_SKIP_CHECK=1 +# Базовый make_hdd копирует дерево без host mtime. Повторная запись с -m +# задаёт контролируемые FAT date/time до включения readonly. +mcopy -m -o "$TEMP_DIR/MSRC/META.BIN" z:/MSRC/META.BIN +mattrib +r +h +s +a z:/MSRC/META.BIN +python3 "$PROJECT_DIR/tests/p18_metadata_fixture.py" verify-source "$RAW" + +mkdir -p "$(dirname -- "$IMAGE")" +chdman createhd -i "$RAW" -o "$IMAGE" -c none \ + -chs 128,16,32 -ss 512 -f +printf 'P18 metadata HDD ready: %s\n' "$IMAGE" diff --git a/applications/Volkov/tests/make_p19_overwrite_hdd.sh b/applications/Volkov/tests/make_p19_overwrite_hdd.sh new file mode 100755 index 0000000..5714dc1 --- /dev/null +++ b/applications/Volkov/tests/make_p19_overwrite_hdd.sh @@ -0,0 +1,30 @@ +#!/bin/sh +# Создаёт HDD для target-пробы транзакционного overwrite. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +IMAGE=${1:-$PROJECT_DIR/build/hdd/p19_overwrite.chd} +PROBE=${2:-$PROJECT_DIR/build/tests/p19/delrt.exe} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-p19.XXXXXX") +BASE_CHD="$TEMP_DIR/base.chd" +RAW="$TEMP_DIR/p19.raw" +MTC="$TEMP_DIR/mtools.conf" + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +python3 "$PROJECT_DIR/tests/p19_overwrite_fixture.py" create "$TEMP_DIR" +"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$BASE_CHD" \ + "$PROBE" "$TEMP_DIR/SOURCE" "$TEMP_DIR/TARGET" +chdman extractraw -i "$BASE_CHD" -o "$RAW" -f >/dev/null +printf 'drive z: file="%s" partition=1\n' "$RAW" > "$MTC" +export MTOOLSRC="$MTC" MTOOLS_SKIP_CHECK=1 +mattrib +r z:/TARGET/READONLY.BIN +mkdir -p "$(dirname -- "$IMAGE")" +chdman createhd -i "$RAW" -o "$IMAGE" -c none \ + -chs 128,16,32 -ss 512 -f +printf 'P19 overwrite HDD ready: %s\n' "$IMAGE" diff --git a/applications/Volkov/tests/make_p19_policy_hdd.sh b/applications/Volkov/tests/make_p19_policy_hdd.sh new file mode 100755 index 0000000..bab9984 --- /dev/null +++ b/applications/Volkov/tests/make_p19_policy_hdd.sh @@ -0,0 +1,30 @@ +#!/bin/sh +# Создаёт HDD полного UI-сценария conflict-policy P19. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +IMAGE=${1:-$PROJECT_DIR/build/hdd/p19_policy.chd} +COMMANDER=${2:-$PROJECT_DIR/build/sprcmd.exe} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-p19-ui.XXXXXX") +BASE_CHD="$TEMP_DIR/base.chd" +RAW="$TEMP_DIR/p19.raw" +MTC="$TEMP_DIR/mtools.conf" + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +python3 "$PROJECT_DIR/tests/p19_policy_fixture.py" create "$TEMP_DIR" +"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$BASE_CHD" \ + "$COMMANDER" "$TEMP_DIR/P19SRC" "$TEMP_DIR/P19DST" +chdman extractraw -i "$BASE_CHD" -o "$RAW" -f >/dev/null +printf 'drive z: file="%s" partition=1\n' "$RAW" > "$MTC" +export MTOOLSRC="$MTC" MTOOLS_SKIP_CHECK=1 +mattrib +r z:/P19DST/RONLY.BIN +mkdir -p "$(dirname -- "$IMAGE")" +chdman createhd -i "$RAW" -o "$IMAGE" -c none \ + -chs 128,16,32 -ss 512 -f +printf 'P19 policy HDD ready: %s\n' "$IMAGE" diff --git a/applications/Volkov/tests/make_p3_stress_floppy.sh b/applications/Volkov/tests/make_p3_stress_floppy.sh new file mode 100644 index 0000000..16b47ed --- /dev/null +++ b/applications/Volkov/tests/make_p3_stress_floppy.sh @@ -0,0 +1,46 @@ +#!/bin/sh +# Создаёт стандартную FAT12-дискету для P3 stress-теста. +# Аргументы: [образ [исполняемый файл]]; по умолчанию — mc.img и Commander. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +DEFAULT_IMAGE="$PROJECT_DIR/../../mame/v306/IMG/mc.img" +IMAGE=${1:-$DEFAULT_IMAGE} +EXECUTABLE=${2:-$PROJECT_DIR/build/sprcmd.exe} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-p3.XXXXXX") + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +mkdir "$TEMP_DIR/MANY" "$TEMP_DIR/BIGDIR" + +i=0 +while [ "$i" -lt 40 ]; do + name=$(printf 'M%07d.TXT' "$i") + cp "$PROJECT_DIR/tests/p3_data/P3ROOT.TXT" "$TEMP_DIR/MANY/$name" + i=$((i + 1)) +done + +i=0 +while [ "$i" -lt 680 ]; do + name=$(printf 'B%07d.DAT' "$i") + cp "$PROJECT_DIR/tests/p3_data/P3ROOT.TXT" "$TEMP_DIR/BIGDIR/$name" + i=$((i + 1)) +done + +mformat -C -i "$IMAGE" -f 1440 -v SPRCMD :: +mcopy -i "$IMAGE" "$EXECUTABLE" :: +mmd -i "$IMAGE" ::EMPTY +mmd -i "$IMAGE" ::TESTDIR +mcopy -i "$IMAGE" "$PROJECT_DIR/tests/p3_data/P3ROOT.TXT" :: +mcopy -i "$IMAGE" "$PROJECT_DIR/tests/p3_data/ZETA.BIN" :: +mcopy -i "$IMAGE" "$PROJECT_DIR/tests/p3_data/INNER.TXT" ::TESTDIR/ +mcopy -i "$IMAGE" -s "$TEMP_DIR/MANY" :: +mcopy -i "$IMAGE" -s "$TEMP_DIR/BIGDIR" :: + +mdir -i "$IMAGE" :: +echo "P3 stress floppy ready: $IMAGE (executable: $EXECUTABLE)" diff --git a/applications/Volkov/tests/make_p3_stress_hdd.sh b/applications/Volkov/tests/make_p3_stress_hdd.sh new file mode 100755 index 0000000..02e20e1 --- /dev/null +++ b/applications/Volkov/tests/make_p3_stress_hdd.sh @@ -0,0 +1,53 @@ +#!/bin/sh +# Создаёт отдельный FAT16 HDD для интеграционных тестов Commander. +# Аргументы: [образ [исполняемый файл]]; по умолчанию — локальный build/HDD. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +DEFAULT_IMAGE="$PROJECT_DIR/build/hdd/p3_stress.chd" +IMAGE=${1:-$DEFAULT_IMAGE} +EXECUTABLE=${2:-$PROJECT_DIR/build/sprcmd.exe} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-hdd.XXXXXX") +FIXTURE_ROOT="$TEMP_DIR/P3DATA" + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +mkdir -p "$(dirname -- "$IMAGE")" +mkdir "$FIXTURE_ROOT" +mkdir "$FIXTURE_ROOT/EMPTY" "$FIXTURE_ROOT/TESTDIR" \ + "$FIXTURE_ROOT/MANY" "$FIXTURE_ROOT/BIGDIR" + +cp "$PROJECT_DIR/tests/p3_data/INNER.TXT" \ + "$FIXTURE_ROOT/TESTDIR/INNER.TXT" + +i=0 +while [ "$i" -lt 40 ]; do + name=$(printf 'M%07d.TXT' "$i") + cp "$PROJECT_DIR/tests/p3_data/P3ROOT.TXT" \ + "$FIXTURE_ROOT/MANY/$name" + i=$((i + 1)) +done + +i=0 +while [ "$i" -lt 680 ]; do + name=$(printf 'B%07d.DAT' "$i") + cp "$PROJECT_DIR/tests/p3_data/P3ROOT.TXT" \ + "$FIXTURE_ROOT/BIGDIR/$name" + i=$((i + 1)) +done + +cp "$PROJECT_DIR/tests/p3_data/P3ROOT.TXT" "$FIXTURE_ROOT/P3ROOT.TXT" +cp "$PROJECT_DIR/tests/p3_data/ZETA.BIN" "$FIXTURE_ROOT/ZETA.BIN" + +"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \ + "$EXECUTABLE" \ + "$FIXTURE_ROOT/EMPTY" "$FIXTURE_ROOT/TESTDIR" \ + "$FIXTURE_ROOT/MANY" "$FIXTURE_ROOT/BIGDIR" \ + "$FIXTURE_ROOT/P3ROOT.TXT" "$FIXTURE_ROOT/ZETA.BIN" + +echo "P3 stress HDD ready: $IMAGE (D:, executable: SPRCMD.EXE)" diff --git a/applications/Volkov/tests/make_p4_copy_hdd.sh b/applications/Volkov/tests/make_p4_copy_hdd.sh new file mode 100755 index 0000000..d736d75 --- /dev/null +++ b/applications/Volkov/tests/make_p4_copy_hdd.sh @@ -0,0 +1,26 @@ +#!/bin/sh +# Создаёт отдельный FAT16 HDD для интеграционного теста F5. +# Аргументы: [образ [исполняемый файл]]. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +DEFAULT_IMAGE="$PROJECT_DIR/build/hdd/p4_copy.chd" +IMAGE=${1:-$DEFAULT_IMAGE} +EXECUTABLE=${2:-$PROJECT_DIR/build/sprcmd.exe} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-copy.XXXXXX") +FIXTURE_ROOT="$TEMP_DIR/P4DATA" + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +mkdir -p "$(dirname -- "$IMAGE")" +python3 "$PROJECT_DIR/tests/p4_fixture.py" create "$FIXTURE_ROOT" + +"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \ + "$EXECUTABLE" "$FIXTURE_ROOT/COPYFROM" "$FIXTURE_ROOT/COPYTO" + +echo "P4 copy HDD ready: $IMAGE (D:, executable: SPRCMD.EXE)" diff --git a/applications/Volkov/tests/make_p4_exec_hdd.sh b/applications/Volkov/tests/make_p4_exec_hdd.sh new file mode 100755 index 0000000..2628cd3 --- /dev/null +++ b/applications/Volkov/tests/make_p4_exec_hdd.sh @@ -0,0 +1,31 @@ +#!/bin/sh +# Создаёт отдельный HDD для EXEC/restore-теста P4.2. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +DEFAULT_IMAGE="$PROJECT_DIR/build/hdd/p4_exec.chd" +IMAGE=${1:-$DEFAULT_IMAGE} +COMMANDER=${2:-$PROJECT_DIR/build/sprcmd.exe} +CHILD=${3:-$PROJECT_DIR/tests/p4_exec_child/p4child.exe} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-exec.XXXXXX") +FIXTURE_ROOT="$TEMP_DIR/P4EXEC" + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +mkdir -p "$(dirname -- "$IMAGE")" +mkdir -p "$FIXTURE_ROOT/EXECLEFT" "$FIXTURE_ROOT/EXECRGHT" +cp "$CHILD" "$FIXTURE_ROOT/EXECLEFT/P4CHILD.EXE" +cp "$PROJECT_DIR/tests/p3_data/P3ROOT.TXT" \ + "$FIXTURE_ROOT/EXECLEFT/BROKEN.EXE" +cp "$PROJECT_DIR/tests/p3_data/INNER.TXT" \ + "$FIXTURE_ROOT/EXECLEFT/NOTE.TXT" + +"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \ + "$COMMANDER" "$FIXTURE_ROOT/EXECLEFT" "$FIXTURE_ROOT/EXECRGHT" + +echo "P4 EXEC HDD ready: $IMAGE (D:, child: EXECLEFT/P4CHILD.EXE)" diff --git a/applications/Volkov/tests/make_p5_copy_faults_hdd.sh b/applications/Volkov/tests/make_p5_copy_faults_hdd.sh new file mode 100755 index 0000000..7d07494 --- /dev/null +++ b/applications/Volkov/tests/make_p5_copy_faults_hdd.sh @@ -0,0 +1,25 @@ +#!/bin/sh +# Создаёт HDD с отдельной матрицей отказов банкового copy-job. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +DEFAULT_IMAGE="$PROJECT_DIR/build/hdd/p5_copy_faults.chd" +IMAGE=${1:-$DEFAULT_IMAGE} +EXECUTABLE=${2:-$PROJECT_DIR/build/tests/cperr.exe} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-copy-faults.XXXXXX") +FIXTURE_ROOT="$TEMP_DIR/P5COPY" + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +mkdir -p "$(dirname -- "$IMAGE")" +python3 "$PROJECT_DIR/tests/p5_copy_faults_fixture.py" create "$FIXTURE_ROOT" + +"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \ + "$EXECUTABLE" "$FIXTURE_ROOT/SOURCE" "$FIXTURE_ROOT/TARGET" + +echo "P5 copy-fault HDD ready: $IMAGE" diff --git a/applications/Volkov/tests/make_p5_enospc_hdd.sh b/applications/Volkov/tests/make_p5_enospc_hdd.sh new file mode 100755 index 0000000..0381089 --- /dev/null +++ b/applications/Volkov/tests/make_p5_enospc_hdd.sh @@ -0,0 +1,27 @@ +#!/bin/sh +# Создаёт почти полный HDD для реальной ошибки записи ENOSPC. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +DEFAULT_IMAGE="$PROJECT_DIR/build/hdd/p5_enospc.chd" +IMAGE=${1:-$DEFAULT_IMAGE} +EXECUTABLE=${2:-$PROJECT_DIR/build/sprcmd.exe} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-enospc.XXXXXX") +FIXTURE_ROOT="$TEMP_DIR/ENOSPC" + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +mkdir -p "$(dirname -- "$IMAGE")" +python3 "$PROJECT_DIR/tests/p5_enospc_fixture.py" create "$FIXTURE_ROOT" +python3 "$PROJECT_DIR/tests/p5_enospc_fixture.py" verify-initial "$FIXTURE_ROOT" + +"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \ + "$EXECUTABLE" "$FIXTURE_ROOT/SOURCE" "$FIXTURE_ROOT/TARGET" \ + "$FIXTURE_ROOT/FILLER.BIN" + +echo "P5 ENOSPC HDD ready: $IMAGE" diff --git a/applications/Volkov/tests/make_p5_hdd.sh b/applications/Volkov/tests/make_p5_hdd.sh new file mode 100755 index 0000000..9c92d20 --- /dev/null +++ b/applications/Volkov/tests/make_p5_hdd.sh @@ -0,0 +1,26 @@ +#!/bin/sh +# Создаёт HDD для P5: 20 копирований, 100 refresh и cleanup-циклы. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +DEFAULT_IMAGE="$PROJECT_DIR/build/hdd/p5_stability.chd" +IMAGE=${1:-$DEFAULT_IMAGE} +EXECUTABLE=${2:-$PROJECT_DIR/build/sprcmd.exe} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-p5.XXXXXX") +FIXTURE_ROOT="$TEMP_DIR/P5DATA" + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +mkdir -p "$(dirname -- "$IMAGE")" +python3 "$PROJECT_DIR/tests/p5_fixture.py" create "$FIXTURE_ROOT" +python3 "$PROJECT_DIR/tests/p5_fixture.py" verify-initial "$FIXTURE_ROOT" + +"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \ + "$EXECUTABLE" "$FIXTURE_ROOT/SOURCE" "$FIXTURE_ROOT/TARGET" + +echo "P5 stability HDD ready: $IMAGE" diff --git a/applications/Volkov/tests/make_p5_media_hdd.sh b/applications/Volkov/tests/make_p5_media_hdd.sh new file mode 100755 index 0000000..4cc5186 --- /dev/null +++ b/applications/Volkov/tests/make_p5_media_hdd.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# Создаёт HDD для физического unload/load во время F5. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +DEFAULT_IMAGE="$PROJECT_DIR/build/hdd/p5_media.chd" +IMAGE=${1:-$DEFAULT_IMAGE} +EXECUTABLE=${2:-$PROJECT_DIR/build/sprcmd.exe} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-media.XXXXXX") +FIXTURE_ROOT="$TEMP_DIR/P5MEDIA" + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +mkdir -p "$(dirname -- "$IMAGE")" +python3 "$PROJECT_DIR/tests/p5_media_fixture.py" create "$FIXTURE_ROOT" +"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \ + "$EXECUTABLE" "$FIXTURE_ROOT/MEDIA" + +echo "P5 media-change HDD ready: $IMAGE" diff --git a/applications/Volkov/tests/make_p5_panel_edges_hdd.sh b/applications/Volkov/tests/make_p5_panel_edges_hdd.sh new file mode 100755 index 0000000..f482b6e --- /dev/null +++ b/applications/Volkov/tests/make_p5_panel_edges_hdd.sh @@ -0,0 +1,38 @@ +#!/bin/sh +# Создаёт каталоги с итоговыми panel.count ровно 27 и 28 (включая `..`). + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +DEFAULT_IMAGE="$PROJECT_DIR/build/hdd/p5_panel_edges.chd" +IMAGE=${1:-$DEFAULT_IMAGE} +EXECUTABLE=${2:-$PROJECT_DIR/build/sprcmd.exe} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-panel-edge.XXXXXX") +FIXTURE_ROOT="$TEMP_DIR/EDGES" + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +mkdir -p "$(dirname -- "$IMAGE")" "$FIXTURE_ROOT/N27" "$FIXTURE_ROOT/N28" + +i=0 +while [ "$i" -lt 26 ]; do + name=$(printf 'A%07d.TXT' "$i") + cp "$PROJECT_DIR/tests/p3_data/P3ROOT.TXT" "$FIXTURE_ROOT/N27/$name" + i=$((i + 1)) +done + +i=0 +while [ "$i" -lt 27 ]; do + name=$(printf 'B%07d.TXT' "$i") + cp "$PROJECT_DIR/tests/p3_data/P3ROOT.TXT" "$FIXTURE_ROOT/N28/$name" + i=$((i + 1)) +done + +"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \ + "$EXECUTABLE" "$FIXTURE_ROOT/N27" "$FIXTURE_ROOT/N28" + +echo "P5 panel-edge HDD ready: N27=26+parent, N28=27+parent" diff --git a/applications/Volkov/tests/make_p5_viewer_hdd.sh b/applications/Volkov/tests/make_p5_viewer_hdd.sh new file mode 100755 index 0000000..87e1115 --- /dev/null +++ b/applications/Volkov/tests/make_p5_viewer_hdd.sh @@ -0,0 +1,28 @@ +#!/bin/sh +# Создаёт HDD для запуска реального полноэкранного mdview2 из Commander. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +DEFAULT_IMAGE="$PROJECT_DIR/build/hdd/p5_viewer.chd" +IMAGE=${1:-$DEFAULT_IMAGE} +COMMANDER=${2:-$PROJECT_DIR/build/sprcmd.exe} +VIEWER=${3:-$PROJECT_DIR/../../examples/mdview2/mdview2.exe} +README=${4:-$PROJECT_DIR/../../examples/mdview2/README.md} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-viewer.XXXXXX") +FIXTURE_ROOT="$TEMP_DIR/VIEWDATA" + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +mkdir -p "$(dirname -- "$IMAGE")" "$FIXTURE_ROOT/VIEWER" +cp "$VIEWER" "$FIXTURE_ROOT/VIEWER/MDVIEW2.EXE" +cp "$README" "$FIXTURE_ROOT/VIEWER/README.MD" + +"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \ + "$COMMANDER" "$FIXTURE_ROOT/VIEWER" + +echo "P5 viewer HDD ready: VIEWER/MDVIEW2.EXE + README.MD" diff --git a/applications/Volkov/tests/make_p6_sort_hdd.sh b/applications/Volkov/tests/make_p6_sort_hdd.sh new file mode 100755 index 0000000..82bf14e --- /dev/null +++ b/applications/Volkov/tests/make_p6_sort_hdd.sh @@ -0,0 +1,25 @@ +#!/bin/sh +# Собирает HDD с target-регрессией и двумя каталогами UI-сортировки. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +IMAGE=${1:-$PROJECT_DIR/build/hdd/p6_sort.chd} +EXECUTABLE=${2:-$PROJECT_DIR/build/sprcmd.exe} +PROBE=${3:-$PROJECT_DIR/build/tests/sortrt.exe} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-sort.XXXXXX") +FIXTURE_ROOT="$TEMP_DIR/SORTDATA" + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +mkdir -p "$(dirname -- "$IMAGE")" +python3 "$PROJECT_DIR/tests/p6_sort_fixture.py" create "$FIXTURE_ROOT" + +"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \ + "$EXECUTABLE" "$PROBE" "$FIXTURE_ROOT/SORTL" "$FIXTURE_ROOT/SORTR" + +echo "P6 sort HDD ready: $IMAGE" diff --git a/applications/Volkov/tests/make_p7_select_hdd.sh b/applications/Volkov/tests/make_p7_select_hdd.sh new file mode 100755 index 0000000..5b594d0 --- /dev/null +++ b/applications/Volkov/tests/make_p7_select_hdd.sh @@ -0,0 +1,23 @@ +#!/bin/sh +# Собирает HDD target/UI-регрессии выбора записей. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +IMAGE=${1:-$PROJECT_DIR/build/hdd/p7_select.chd} +EXECUTABLE=${2:-$PROJECT_DIR/build/sprcmd.exe} +PROBE=${3:-$PROJECT_DIR/build/tests/selrt.exe} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-select.XXXXXX") +FIXTURE_ROOT="$TEMP_DIR/SELDATA" + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +mkdir -p "$(dirname -- "$IMAGE")" +python3 "$PROJECT_DIR/tests/p7_select_fixture.py" create "$FIXTURE_ROOT" +"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \ + "$EXECUTABLE" "$PROBE" "$FIXTURE_ROOT/SELECT" +echo "P7 selection HDD ready: $IMAGE" diff --git a/applications/Volkov/tests/make_p9_mkdir_hdd.sh b/applications/Volkov/tests/make_p9_mkdir_hdd.sh new file mode 100755 index 0000000..f4bcaa1 --- /dev/null +++ b/applications/Volkov/tests/make_p9_mkdir_hdd.sh @@ -0,0 +1,23 @@ +#!/bin/sh +# Собирает HDD target/UI-регрессии F7 mkdir. + +set -eu + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +IMAGE=${1:-$PROJECT_DIR/build/hdd/p9_mkdir.chd} +EXECUTABLE=${2:-$PROJECT_DIR/build/sprcmd.exe} +PROBE=${3:-$PROJECT_DIR/build/tests/mkrt.exe} +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-mkdir.XXXXXX") +FIXTURE_ROOT="$TEMP_DIR/MKDATA" + +cleanup() +{ + rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +mkdir -p "$(dirname -- "$IMAGE")" +python3 "$PROJECT_DIR/tests/p9_mkdir_fixture.py" create "$FIXTURE_ROOT" +"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \ + "$EXECUTABLE" "$PROBE" "$FIXTURE_ROOT/MKTEST" +echo "P9 mkdir HDD ready: $IMAGE" diff --git a/applications/Volkov/tests/mame_image_probe.lua b/applications/Volkov/tests/mame_image_probe.lua new file mode 100644 index 0000000..c608fcc --- /dev/null +++ b/applications/Volkov/tests/mame_image_probe.lua @@ -0,0 +1,15 @@ +-- Диагностический список MAME image-устройств Sprinter и их свойств. + +local done = false + +emu.register_periodic(function() + if done then return end + done = true + for tag, image in pairs(manager.machine.images) do + print(string.format( + "[image-probe] tag=%s instance=%s exists=%s readonly=%s file=%s", + tag, image.instance_name, tostring(image.exists), + tostring(image.readonly), image.filename)) + end + manager.machine:exit() +end) diff --git a/applications/Volkov/tests/mame_p10_rename.lua b/applications/Volkov/tests/mame_p10_rename.lua new file mode 100644 index 0000000..8edaabe --- /dev/null +++ b/applications/Volkov/tests/mame_p10_rename.lua @@ -0,0 +1,148 @@ +-- 0.2/P10: F6 local rename, unchanged name, collision и group guard. + +local KB = ":kbd:ms_naturl:" +local HOLD = 0.06 +local events = {} + +local function add(t, tag, mask, value) + events[#events + 1] = {t, KB .. tag, mask, value} +end + +local function key(t, tag, mask) + add(t, tag, mask, 1) + add(t + HOLD, tag, mask, 0) +end + +local function chord(t, mod_tag, mod_mask, key_tag, key_mask) + add(t, mod_tag, mod_mask, 1) + add(t + 0.02, key_tag, key_mask, 1) + add(t + 0.08, key_tag, key_mask, 0) + add(t + 0.10, mod_tag, mod_mask, 0) +end + +local chars = { + a={"P1.6",0x04}, b={"P1.5",0x10}, c={"P1.4",0x08}, + d={"P1.4",0x04}, e={"P1.4",0x20}, f={"P1.5",0x02}, + i={"P1.4",0x01}, l={"P2.2",0x08}, m={"P2.0",0x10}, + n={"P2.0",0x08}, p={"P2.3",0x20}, r={"P1.5",0x01}, + s={"P1.2",0x08}, t={"P1.5",0x20}, x={"P1.2",0x10}, + ["."]={"P2.2",0x10}, ["\n"]={"P2.1",0x10}, + [":"]={"P2.3",0x02,true} +} + +local function type_text(t, value) + for i = 1, #value do + local ch = value:sub(i, i) + local def = chars[ch] or chars[ch:lower()] + local shifted = def[3] or ch ~= ch:lower() + if shifted then + chord(t, "P1.7", 0x02, def[1], def[2]) + else + key(t, def[1], def[2]) + end + t = t + 0.14 + end +end + +local function backspaces(t, count) + for _ = 1, count do + key(t, "P2.1", 0x20) + t = t + 0.18 + end + return t +end + +type_text(8.0, "d:\n") +type_text(9.0, "RENRT.EXE\n") +type_text(12.0, "SPRCMD.EXE\n") + +key(18.0, "P2.1", 0x10) -- enter RENTEST. +key(20.0, "P2.3", 0x10) -- Down x4 -> UIOLD.TXT. +key(20.5, "P2.3", 0x10) +key(21.0, "P2.3", 0x10) +key(21.5, "P2.3", 0x10) +key(23.0, "P2.1", 0x80) -- F6. +local t = backspaces(24.0, 9) +type_text(t, "final.bin") +key(t + 1.4, "P2.1", 0x10) + +key(32.0, "P2.1", 0x80) -- F6, имя без изменений. +key(33.0, "P2.1", 0x10) + +key(36.0, "P2.1", 0x80) -- F6, collision с EXIST.TXT. +t = backspaces(37.0, 9) +type_text(t, "exist.txt") +key(t + 1.4, "P2.1", 0x10) + +key(44.0, "P2.5", 0x01) -- Home -> `..`. +key(45.0, "P2.1", 0x80) -- F6 guard virtual. +key(48.0, "P2.3", 0x10) -- NEWDIR. +key(49.0, "P2.6", 0x01) -- Insert dir; cursor -> file. +key(50.0, "P2.1", 0x80) -- group ambiguity guard. + +key(54.0, "P2.3", 0x80) -- F10. +key(56.0, "P2.1", 0x10) + +table.sort(events, function(left, right) return left[1] < right[1] end) + +local snap_times = { + 19.0, -- target probe already renamed file and directory; + 23.5, -- dialog default UIOLD.TXT; + 30.5, -- FINAL.BIN created and focused; + 34.0, -- unchanged name skipped; + 42.0, -- existing target rejected; + 46.0, -- virtual `..` rejected; + 51.0, -- selected dir elsewhere makes group action explicit; + 58.0 -- clean DSS prompt. +} +local snaps_done = {} +local start_time = nil +local ports = nil +local index = 1 +local field_cache = {} + +local function now() + local value = manager.machine.time + return value.seconds + value.attoseconds / 1e18 +end + +local function field(tag, mask) + local cache_key = tag .. "/" .. mask + if field_cache[cache_key] then return field_cache[cache_key] end + for _, candidate in pairs(ports[tag].fields) do + if candidate.mask == mask then + field_cache[cache_key] = candidate + return candidate + end + end + error("Не найдено поле " .. cache_key) +end + +emu.add_machine_reset_notifier(function() + start_time = now() +end) + +emu.register_periodic(function() + if not start_time then return end + if not ports then ports = manager.machine.ioport.ports end + local elapsed = now() - start_time + + while index <= #events and elapsed >= events[index][1] do + local event = events[index] + field(event[2], event[3]):set_value(event[4]) + index = index + 1 + end + + for _, snap_time in ipairs(snap_times) do + if not snaps_done[snap_time] and elapsed >= snap_time then + snaps_done[snap_time] = true + manager.machine.video:snapshot() + print("[p10-rename] snapshot t=" .. elapsed) + end + end + + if elapsed >= 60.0 then + print("[p10-rename] exit t=" .. elapsed) + manager.machine:exit() + end +end) diff --git a/applications/Volkov/tests/mame_p11_delete.lua b/applications/Volkov/tests/mame_p11_delete.lua new file mode 100644 index 0000000..4b9d5dd --- /dev/null +++ b/applications/Volkov/tests/mame_p11_delete.lua @@ -0,0 +1,140 @@ +-- 0.2/P11: F8 cancel, file, empty/nonempty directory и guards. + +local KB = ":kbd:ms_naturl:" +local HOLD = 0.06 +local events = {} + +local function add(t, tag, mask, value) + events[#events + 1] = {t, KB .. tag, mask, value} +end + +local function key(t, tag, mask) + add(t, tag, mask, 1) + add(t + HOLD, tag, mask, 0) +end + +local function chord(t, mod_tag, mod_mask, key_tag, key_mask) + add(t, mod_tag, mod_mask, 1) + add(t + 0.02, key_tag, key_mask, 1) + add(t + 0.08, key_tag, key_mask, 0) + add(t + 0.10, mod_tag, mod_mask, 0) +end + +local chars = { + c={"P1.4",0x08}, d={"P1.4",0x04}, e={"P1.4",0x20}, + l={"P2.2",0x08}, m={"P2.0",0x10}, n={"P2.0",0x08}, + p={"P2.3",0x20}, r={"P1.5",0x01}, s={"P1.2",0x08}, + t={"P1.5",0x20}, x={"P1.2",0x10}, ["."]={"P2.2",0x10}, + ["\n"]={"P2.1",0x10}, [":"]={"P2.3",0x02,true} +} + +local function type_text(t, value) + for i = 1, #value do + local ch = value:sub(i, i) + local def = chars[ch] or chars[ch:lower()] + local shifted = def[3] or ch ~= ch:lower() + if shifted then + chord(t, "P1.7", 0x02, def[1], def[2]) + else + key(t, def[1], def[2]) + end + t = t + 0.14 + end +end + +type_text(8.0, "d:\n") +type_text(9.0, "DELRT.EXE\n") +type_text(12.0, "SPRCMD.EXE\n") + +key(18.0, "P2.1", 0x10) -- enter DELTEST. +key(20.0, "P2.3", 0x10) -- Down x4 -> REMOVE.TXT. +key(20.5, "P2.3", 0x10) +key(21.0, "P2.3", 0x10) +key(21.5, "P2.3", 0x10) +key(23.0, "P2.2", 0x80) -- F8 file, then cancel. +key(25.0, "P2.0", 0x08) -- N. + +key(28.0, "P2.2", 0x80) -- F8 file, confirm. +key(30.0, "P2.1", 0x10) + +key(34.0, "P2.3", 0x10) -- EMPTYDIR. +key(35.0, "P2.2", 0x80) +key(37.0, "P2.1", 0x10) + +key(40.0, "P2.3", 0x10) -- NONEMPTY. +key(41.0, "P2.2", 0x80) +key(43.0, "P2.1", 0x10) + +key(46.0, "P2.5", 0x01) -- Home -> `..`. +key(47.0, "P2.2", 0x80) -- virtual guard. +key(50.0, "P2.3", 0x10) -- NONEMPTY. +key(51.0, "P2.6", 0x01) -- select dir, cursor -> KEEP. +key(52.0, "P2.2", 0x80) -- group ambiguity guard. + +key(56.0, "P2.3", 0x80) -- F10. +key(58.0, "P2.1", 0x10) + +table.sort(events, function(left, right) return left[1] < right[1] end) + +local snap_times = { + 19.0, -- target probe left UI fixture intact; + 23.5, -- file confirmation; + 26.0, -- cancel preserved REMOVE.TXT; + 32.0, -- file deleted; + 39.0, -- empty directory deleted; + 45.0, -- nonempty directory rejected; + 48.0, -- virtual `..` rejected; + 53.0, -- ambiguous group rejected; + 60.0 -- clean DSS prompt. +} +local snaps_done = {} +local start_time = nil +local ports = nil +local index = 1 +local field_cache = {} + +local function now() + local value = manager.machine.time + return value.seconds + value.attoseconds / 1e18 +end + +local function field(tag, mask) + local cache_key = tag .. "/" .. mask + if field_cache[cache_key] then return field_cache[cache_key] end + for _, candidate in pairs(ports[tag].fields) do + if candidate.mask == mask then + field_cache[cache_key] = candidate + return candidate + end + end + error("Не найдено поле " .. cache_key) +end + +emu.add_machine_reset_notifier(function() + start_time = now() +end) + +emu.register_periodic(function() + if not start_time then return end + if not ports then ports = manager.machine.ioport.ports end + local elapsed = now() - start_time + + while index <= #events and elapsed >= events[index][1] do + local event = events[index] + field(event[2], event[3]):set_value(event[4]) + index = index + 1 + end + + for _, snap_time in ipairs(snap_times) do + if not snaps_done[snap_time] and elapsed >= snap_time then + snaps_done[snap_time] = true + manager.machine.video:snapshot() + print("[p11-delete] snapshot t=" .. elapsed) + end + end + + if elapsed >= 62.0 then + print("[p11-delete] exit t=" .. elapsed) + manager.machine:exit() + end +end) diff --git a/applications/Volkov/tests/mame_p12_group_copy.lua b/applications/Volkov/tests/mame_p12_group_copy.lua new file mode 100644 index 0000000..80b993e --- /dev/null +++ b/applications/Volkov/tests/mame_p12_group_copy.lua @@ -0,0 +1,125 @@ +-- Регрессия P12/P16: выбранные файлы и каталог копируются одним F5. + +local KB = ":kbd:ms_naturl:" +local HOLD = 0.06 +local events = {} + +local function add(t, tag, mask, value) + events[#events + 1] = {t, KB .. tag, mask, value} +end + +local function key(t, tag, mask) + add(t, tag, mask, 1) + add(t + HOLD, tag, mask, 0) +end + +local function chord(t, mod_tag, mod_mask, key_tag, key_mask) + add(t, mod_tag, mod_mask, 1) + add(t + 0.02, key_tag, key_mask, 1) + add(t + 0.08, key_tag, key_mask, 0) + add(t + 0.10, mod_tag, mod_mask, 0) +end + +local chars = { + c={"P1.4",0x08}, d={"P1.4",0x04}, e={"P1.4",0x20}, + m={"P2.0",0x10}, p={"P2.3",0x20}, r={"P1.5",0x01}, + s={"P1.2",0x08}, x={"P1.2",0x10}, ["."]={"P2.2",0x10}, + ["\n"]={"P2.1",0x10}, [":"]={"P2.3",0x02,true} +} + +local function type_text(t, value) + for i = 1, #value do + local ch = value:sub(i, i) + local def = chars[ch] or chars[ch:lower()] + local shifted = def[3] or ch ~= ch:lower() + if shifted then + chord(t, "P1.7", 0x02, def[1], def[2]) + else + key(t, def[1], def[2]) + end + t = t + 0.14 + end +end + +type_text(8.0, "d:\n") +type_text(10.0, "SPRCMD.EXE\n") + +key(16.0, "P1.6", 0x20) -- Tab -> right, cursor GRDST. +key(17.0, "P2.1", 0x10) -- right enters GRDST. +key(18.0, "P1.6", 0x20) -- Tab -> left. +key(19.0, "P2.3", 0x10) -- left GRDST -> GRSRC. +key(20.0, "P2.1", 0x10) -- enter GRSRC. + +key(23.0, "P2.3", 0x10) -- ADIR. +key(24.0, "P2.6", 0x01) -- select ADIR, cursor A.BIN. +key(25.0, "P2.6", 0x01) -- A.BIN. +key(26.0, "P2.6", 0x01) -- B.BIN. +key(27.0, "P2.6", 0x01) -- C.BIN. +key(30.0, "P2.1", 0x40) -- F5 group success. +key(36.0, "P2.1", 0x40) -- second F5 stops at existing A.BIN. +key(41.0, "P1.6", 0x20) -- show refreshed passive panel. +key(46.0, "P2.3", 0x80) -- F10. +key(48.0, "P2.1", 0x10) + +table.sort(events, function(left, right) return left[1] < right[1] end) + +local snap_times = { + 21.5, -- source/target panels opened; + 28.5, -- ADIR + three files selected; + 34.0, -- top-level files and selected directory copied; + 39.0, -- repeat stopped at existing target; + 43.0, -- passive panel shows three files; + 50.0 -- clean DSS prompt. +} +local snaps_done = {} +local start_time = nil +local ports = nil +local index = 1 +local field_cache = {} + +local function now() + local value = manager.machine.time + return value.seconds + value.attoseconds / 1e18 +end + +local function field(tag, mask) + local cache_key = tag .. "/" .. mask + if field_cache[cache_key] then return field_cache[cache_key] end + for _, candidate in pairs(ports[tag].fields) do + if candidate.mask == mask then + field_cache[cache_key] = candidate + return candidate + end + end + error("Не найдено поле " .. cache_key) +end + + +emu.add_machine_reset_notifier(function() + start_time = now() +end) + +emu.register_periodic(function() + if not start_time then return end + if not ports then ports = manager.machine.ioport.ports end + local elapsed = now() - start_time + + while index <= #events and elapsed >= events[index][1] do + local event = events[index] + field(event[2], event[3]):set_value(event[4]) + index = index + 1 + end + + for _, snap_time in ipairs(snap_times) do + if not snaps_done[snap_time] and elapsed >= snap_time then + snaps_done[snap_time] = true + manager.machine.video:snapshot() + print("[p12-group-copy] snapshot t=" .. elapsed) + end + end + + if elapsed >= 52.0 then + print("[p12-group-copy] exit t=" .. elapsed) + manager.machine:exit() + end +end) diff --git a/applications/Volkov/tests/mame_p13_group_delete.lua b/applications/Volkov/tests/mame_p13_group_delete.lua new file mode 100644 index 0000000..ae4312e --- /dev/null +++ b/applications/Volkov/tests/mame_p13_group_delete.lua @@ -0,0 +1,121 @@ +-- 0.2/P13 regression: file-only group F8 leaves unselected directories. + +local KB = ":kbd:ms_naturl:" +local HOLD = 0.06 +local events = {} + +local function add(t, tag, mask, value) + events[#events + 1] = {t, KB .. tag, mask, value} +end + +local function key(t, tag, mask) + add(t, tag, mask, 1) + add(t + HOLD, tag, mask, 0) +end + +local function chord(t, mod_tag, mod_mask, key_tag, key_mask) + add(t, mod_tag, mod_mask, 1) + add(t + 0.02, key_tag, key_mask, 1) + add(t + 0.08, key_tag, key_mask, 0) + add(t + 0.10, mod_tag, mod_mask, 0) +end + +local chars = { + c={"P1.4",0x08}, d={"P1.4",0x04}, e={"P1.4",0x20}, + m={"P2.0",0x10}, p={"P2.3",0x20}, r={"P1.5",0x01}, + s={"P1.2",0x08}, x={"P1.2",0x10}, ["."]={"P2.2",0x10}, + ["\n"]={"P2.1",0x10}, [":"]={"P2.3",0x02,true} +} + +local function type_text(t, value) + for i = 1, #value do + local ch = value:sub(i, i) + local def = chars[ch] or chars[ch:lower()] + local shifted = def[3] or ch ~= ch:lower() + if shifted then + chord(t, "P1.7", 0x02, def[1], def[2]) + else + key(t, def[1], def[2]) + end + t = t + 0.14 + end +end + +type_text(8.0, "d:\n") +type_text(10.0, "SPRCMD.EXE\n") + +key(17.0, "P2.1", 0x10) -- enter GDEL. +key(20.0, "P2.3", 0x10) -- ADIR. +key(21.0, "P2.3", 0x10) -- EMPTYDIR. +key(22.0, "P2.3", 0x10) -- A.TXT. +key(23.0, "P2.6", 0x01) -- select A.TXT -> B.BIN. +key(24.0, "P2.6", 0x01) -- select B.BIN -> KEEP.TXT. +key(27.0, "P2.2", 0x80) -- F8 group, cancel. +key(29.0, "P2.0", 0x08) -- N. +key(32.0, "P2.2", 0x80) -- F8 group, confirm. +key(34.0, "P2.1", 0x10) + +key(42.0, "P2.3", 0x80) -- F10. +key(44.0, "P2.1", 0x10) + +table.sort(events, function(left, right) return left[1] < right[1] end) + +local snap_times = { + 19.0, -- исходное дерево; + 25.0, -- два каталога и два файла выбраны; + 27.5, -- group confirmation; + 30.0, -- cancel сохранил всё; + 36.0, -- файлы удалены, невыбранные каталоги сохранены; + 46.0 -- clean DSS prompt. +} +local snaps_done = {} +local start_time = nil +local ports = nil +local index = 1 +local field_cache = {} + +local function now() + local value = manager.machine.time + return value.seconds + value.attoseconds / 1e18 +end + +local function field(tag, mask) + local cache_key = tag .. "/" .. mask + if field_cache[cache_key] then return field_cache[cache_key] end + for _, candidate in pairs(ports[tag].fields) do + if candidate.mask == mask then + field_cache[cache_key] = candidate + return candidate + end + end + error("Не найдено поле " .. cache_key) +end + +emu.add_machine_reset_notifier(function() + start_time = now() +end) + +emu.register_periodic(function() + if not start_time then return end + if not ports then ports = manager.machine.ioport.ports end + local elapsed = now() - start_time + + while index <= #events and elapsed >= events[index][1] do + local event = events[index] + field(event[2], event[3]):set_value(event[4]) + index = index + 1 + end + + for _, snap_time in ipairs(snap_times) do + if not snaps_done[snap_time] and elapsed >= snap_time then + snaps_done[snap_time] = true + manager.machine.video:snapshot() + print("[p13-group-delete] snapshot t=" .. elapsed) + end + end + + if elapsed >= 48.0 then + print("[p13-group-delete] exit t=" .. elapsed) + manager.machine:exit() + end +end) diff --git a/applications/Volkov/tests/mame_p14_default_colors.lua b/applications/Volkov/tests/mame_p14_default_colors.lua new file mode 100644 index 0000000..b879534 --- /dev/null +++ b/applications/Volkov/tests/mame_p14_default_colors.lua @@ -0,0 +1,106 @@ +-- 1.0/P14: стандартные цвета каталогов, EXE и остальных файлов. + +local KB = ":kbd:ms_naturl:" +local HOLD = 0.06 +local events = {} + +local function add(t, tag, mask, value) + events[#events + 1] = {t, KB .. tag, mask, value} +end + +local function key(t, tag, mask) + add(t, tag, mask, 1) + add(t + HOLD, tag, mask, 0) +end + +local function chord(t, mod_tag, mod_mask, key_tag, key_mask) + add(t, mod_tag, mod_mask, 1) + add(t + 0.02, key_tag, key_mask, 1) + add(t + 0.08, key_tag, key_mask, 0) + add(t + 0.10, mod_tag, mod_mask, 0) +end + +local chars = { + c={"P1.4",0x08}, d={"P1.4",0x04}, e={"P1.4",0x20}, + m={"P2.0",0x10}, p={"P2.3",0x20}, r={"P1.5",0x01}, + s={"P1.2",0x08}, x={"P1.2",0x10}, ["."]={"P2.2",0x10}, + ["\n"]={"P2.1",0x10}, [":"]={"P2.3",0x02,true} +} + +local function type_text(t, value) + for i = 1, #value do + local ch = value:sub(i, i) + local def = chars[ch] or chars[ch:lower()] + local shifted = def[3] or ch ~= ch:lower() + if shifted then + chord(t, "P1.7", 0x02, def[1], def[2]) + else + key(t, def[1], def[2]) + end + t = t + 0.14 + end +end + +type_text(8.0, "d:\n") +type_text(10.0, "SPRCMD.EXE\n") +key(17.0, "P2.1", 0x10) -- enter COLORS. +key(24.0, "P2.3", 0x80) -- F10. +key(26.0, "P2.1", 0x10) -- confirm exit. + +table.sort(events, function(left, right) return left[1] < right[1] end) + +local snap_times = { + 19.0, -- известный набор: dir, EXE, ZIP, no-ext, TXT; + 29.0 -- clean DSS prompt. +} +local snaps_done = {} +local start_time = nil +local ports = nil +local index = 1 +local field_cache = {} + +local function now() + local value = manager.machine.time + return value.seconds + value.attoseconds / 1e18 +end + +local function field(tag, mask) + local cache_key = tag .. "/" .. mask + if field_cache[cache_key] then return field_cache[cache_key] end + for _, candidate in pairs(ports[tag].fields) do + if candidate.mask == mask then + field_cache[cache_key] = candidate + return candidate + end + end + error("Не найдено поле " .. cache_key) +end + +emu.add_machine_reset_notifier(function() + start_time = now() +end) + +emu.register_periodic(function() + if not start_time then return end + if not ports then ports = manager.machine.ioport.ports end + local elapsed = now() - start_time + + while index <= #events and elapsed >= events[index][1] do + local event = events[index] + field(event[2], event[3]):set_value(event[4]) + index = index + 1 + end + + for _, snap_time in ipairs(snap_times) do + if not snaps_done[snap_time] and elapsed >= snap_time then + snaps_done[snap_time] = true + manager.machine.video:snapshot() + print("[p14-default-colors] snapshot t=" .. elapsed) + end + end + + if elapsed >= 31.0 then + print("[p14-default-colors] exit t=" .. elapsed) + manager.machine:exit() + end +end) diff --git a/applications/Volkov/tests/mame_p15_tree_cancel.lua b/applications/Volkov/tests/mame_p15_tree_cancel.lua new file mode 100644 index 0000000..0ee4424 --- /dev/null +++ b/applications/Volkov/tests/mame_p15_tree_cancel.lua @@ -0,0 +1,105 @@ +-- 0.2/P15 hardening: Esc посреди 4-МБ файла tree-job. + +local KB = ":kbd:ms_naturl:" +local HOLD = 0.06 +local events = {} + +local function add(t, tag, mask, value) + events[#events + 1] = {t, KB .. tag, mask, value} +end + +local function key(t, tag, mask) + add(t, tag, mask, 1) + add(t + HOLD, tag, mask, 0) +end + +local function chord(t, mod_tag, mod_mask, key_tag, key_mask) + add(t, mod_tag, mod_mask, 1) + add(t + 0.02, key_tag, key_mask, 1) + add(t + 0.08, key_tag, key_mask, 0) + add(t + 0.10, mod_tag, mod_mask, 0) +end + +local chars = { + c={"P1.4",0x08}, d={"P1.4",0x04}, e={"P1.4",0x20}, + m={"P2.0",0x10}, p={"P2.3",0x20}, r={"P1.5",0x01}, + s={"P1.2",0x08}, x={"P1.2",0x10}, ["."]={"P2.2",0x10}, + ["\n"]={"P2.1",0x10}, [":"]={"P2.3",0x02,true} +} + +local function type_text(t, value) + for i = 1, #value do + local ch = value:sub(i, i) + local def = chars[ch] or chars[ch:lower()] + local shifted = def[3] or ch ~= ch:lower() + if shifted then + chord(t, "P1.7", 0x02, def[1], def[2]) + else + key(t, def[1], def[2]) + end + t = t + 0.14 + end +end + +type_text(8.0, "d:\n") +type_text(10.0, "SPRCMD.EXE\n") +key(16.0, "P1.6", 0x20) -- right. +key(17.0, "P2.1", 0x10) -- enter CDST. +key(18.0, "P1.6", 0x20) -- left. +key(19.0, "P2.3", 0x10) -- CDST -> CSRC. +key(20.0, "P2.1", 0x10) -- enter CSRC. +key(21.0, "P2.3", 0x10) -- TREE. +key(23.0, "P2.1", 0x40) -- F5. +key(24.5, "P1.6", 0x40) -- Esc during BIG.BIN. +key(31.0, "P2.3", 0x80) -- F10. +key(33.0, "P2.1", 0x10) + +table.sort(events, function(left, right) return left[1] < right[1] end) + +local snap_times = {22.0, 27.0, 36.0} +local snaps_done = {} +local start_time = nil +local ports = nil +local index = 1 +local field_cache = {} + +local function now() + local value = manager.machine.time + return value.seconds + value.attoseconds / 1e18 +end + +local function field(tag, mask) + local cache_key = tag .. "/" .. mask + if field_cache[cache_key] then return field_cache[cache_key] end + for _, candidate in pairs(ports[tag].fields) do + if candidate.mask == mask then + field_cache[cache_key] = candidate + return candidate + end + end + error("Не найдено поле " .. cache_key) +end + +emu.add_machine_reset_notifier(function() start_time = now() end) + +emu.register_periodic(function() + if not start_time then return end + if not ports then ports = manager.machine.ioport.ports end + local elapsed = now() - start_time + while index <= #events and elapsed >= events[index][1] do + local event = events[index] + field(event[2], event[3]):set_value(event[4]) + index = index + 1 + end + for _, snap_time in ipairs(snap_times) do + if not snaps_done[snap_time] and elapsed >= snap_time then + snaps_done[snap_time] = true + manager.machine.video:snapshot() + print("[p15-tree-cancel] snapshot t=" .. elapsed) + end + end + if elapsed >= 38.0 then + print("[p15-tree-cancel] exit t=" .. elapsed) + manager.machine:exit() + end +end) diff --git a/applications/Volkov/tests/mame_p15_tree_copy.lua b/applications/Volkov/tests/mame_p15_tree_copy.lua new file mode 100644 index 0000000..74d6b55 --- /dev/null +++ b/applications/Volkov/tests/mame_p15_tree_copy.lua @@ -0,0 +1,119 @@ +-- 0.2/P15: рекурсивный F5 текущего каталога, depth=16 и EEXIST repeat. + +local KB = ":kbd:ms_naturl:" +local HOLD = 0.06 +local events = {} + +local function add(t, tag, mask, value) + events[#events + 1] = {t, KB .. tag, mask, value} +end + +local function key(t, tag, mask) + add(t, tag, mask, 1) + add(t + HOLD, tag, mask, 0) +end + +local function chord(t, mod_tag, mod_mask, key_tag, key_mask) + add(t, mod_tag, mod_mask, 1) + add(t + 0.02, key_tag, key_mask, 1) + add(t + 0.08, key_tag, key_mask, 0) + add(t + 0.10, mod_tag, mod_mask, 0) +end + +local chars = { + c={"P1.4",0x08}, d={"P1.4",0x04}, e={"P1.4",0x20}, + m={"P2.0",0x10}, p={"P2.3",0x20}, r={"P1.5",0x01}, + s={"P1.2",0x08}, x={"P1.2",0x10}, ["."]={"P2.2",0x10}, + ["\n"]={"P2.1",0x10}, [":"]={"P2.3",0x02,true} +} + +local function type_text(t, value) + for i = 1, #value do + local ch = value:sub(i, i) + local def = chars[ch] or chars[ch:lower()] + local shifted = def[3] or ch ~= ch:lower() + if shifted then + chord(t, "P1.7", 0x02, def[1], def[2]) + else + key(t, def[1], def[2]) + end + t = t + 0.14 + end +end + +type_text(8.0, "d:\n") +type_text(10.0, "SPRCMD.EXE\n") + +key(16.0, "P1.6", 0x20) -- right panel. +key(17.0, "P2.1", 0x10) -- enter DST. +key(18.0, "P1.6", 0x20) -- left panel. +key(19.0, "P2.3", 0x10) -- DST -> SRC. +key(20.0, "P2.1", 0x10) -- enter SRC. +key(21.0, "P2.3", 0x10) -- `..` -> TREE. + +key(23.0, "P2.1", 0x40) -- F5 recursive success. +key(34.0, "P2.1", 0x40) -- repeat: target TREE exists. +key(39.0, "P1.6", 0x20) -- show refreshed passive panel. +key(44.0, "P2.3", 0x80) -- F10. +key(46.0, "P2.1", 0x10) + +table.sort(events, function(left, right) return left[1] < right[1] end) + +local snap_times = { + 22.0, -- source TREE and empty destination; + 31.0, -- recursive success; + 37.0, -- repeat stopped on EEXIST; + 41.0, -- passive panel contains TREE; + 49.0 -- clean DSS prompt, including dynamic EMM cleanup. +} +local snaps_done = {} +local start_time = nil +local ports = nil +local index = 1 +local field_cache = {} + +local function now() + local value = manager.machine.time + return value.seconds + value.attoseconds / 1e18 +end + +local function field(tag, mask) + local cache_key = tag .. "/" .. mask + if field_cache[cache_key] then return field_cache[cache_key] end + for _, candidate in pairs(ports[tag].fields) do + if candidate.mask == mask then + field_cache[cache_key] = candidate + return candidate + end + end + error("Не найдено поле " .. cache_key) +end + +emu.add_machine_reset_notifier(function() + start_time = now() +end) + +emu.register_periodic(function() + if not start_time then return end + if not ports then ports = manager.machine.ioport.ports end + local elapsed = now() - start_time + + while index <= #events and elapsed >= events[index][1] do + local event = events[index] + field(event[2], event[3]):set_value(event[4]) + index = index + 1 + end + + for _, snap_time in ipairs(snap_times) do + if not snaps_done[snap_time] and elapsed >= snap_time then + snaps_done[snap_time] = true + manager.machine.video:snapshot() + print("[p15-tree-copy] snapshot t=" .. elapsed) + end + end + + if elapsed >= 51.0 then + print("[p15-tree-copy] exit t=" .. elapsed) + manager.machine:exit() + end +end) diff --git a/applications/Volkov/tests/mame_p15_tree_self.lua b/applications/Volkov/tests/mame_p15_tree_self.lua new file mode 100644 index 0000000..a178c3f --- /dev/null +++ b/applications/Volkov/tests/mame_p15_tree_self.lua @@ -0,0 +1,109 @@ +-- 0.2/P15 hardening: passive target лежит внутри копируемого source root. + +local KB = ":kbd:ms_naturl:" +local HOLD = 0.06 +local events = {} + +local function add(t, tag, mask, value) + events[#events + 1] = {t, KB .. tag, mask, value} +end + + +local function key(t, tag, mask) + add(t, tag, mask, 1) + add(t + HOLD, tag, mask, 0) +end + +local function chord(t, mod_tag, mod_mask, key_tag, key_mask) + add(t, mod_tag, mod_mask, 1) + add(t + 0.02, key_tag, key_mask, 1) + add(t + 0.08, key_tag, key_mask, 0) + add(t + 0.10, mod_tag, mod_mask, 0) +end + +local chars = { + c={"P1.4",0x08}, d={"P1.4",0x04}, e={"P1.4",0x20}, + m={"P2.0",0x10}, p={"P2.3",0x20}, r={"P1.5",0x01}, + s={"P1.2",0x08}, x={"P1.2",0x10}, ["."]={"P2.2",0x10}, + ["\n"]={"P2.1",0x10}, [":"]={"P2.3",0x02,true} +} + +local function type_text(t, value) + for i = 1, #value do + local ch = value:sub(i, i) + local def = chars[ch] or chars[ch:lower()] + local shifted = def[3] or ch ~= ch:lower() + if shifted then + chord(t, "P1.7", 0x02, def[1], def[2]) + else + key(t, def[1], def[2]) + end + t = t + 0.14 + end +end + +type_text(8.0, "d:\n") +type_text(10.0, "SPRCMD.EXE\n") + +key(16.0, "P1.6", 0x20) -- right. +key(17.0, "P2.1", 0x10) -- enter SRC. +key(18.0, "P2.3", 0x10) -- TREE. +key(19.0, "P2.1", 0x10) -- enter TREE. +key(20.0, "P2.3", 0x10) -- INNER. +key(21.0, "P2.1", 0x10) -- enter INNER: passive target. +key(22.0, "P1.6", 0x20) -- left root. +key(23.0, "P2.1", 0x10) -- enter SRC. +key(24.0, "P2.3", 0x10) -- TREE: source root. +key(26.0, "P2.1", 0x40) -- F5 must refuse before mkdir. +key(32.0, "P2.3", 0x80) -- F10. +key(34.0, "P2.1", 0x10) + +table.sort(events, function(left, right) return left[1] < right[1] end) + +local snap_times = {25.0, 29.0, 37.0} +local snaps_done = {} +local start_time = nil +local ports = nil +local index = 1 +local field_cache = {} + +local function now() + local value = manager.machine.time + return value.seconds + value.attoseconds / 1e18 +end + +local function field(tag, mask) + local cache_key = tag .. "/" .. mask + if field_cache[cache_key] then return field_cache[cache_key] end + for _, candidate in pairs(ports[tag].fields) do + if candidate.mask == mask then + field_cache[cache_key] = candidate + return candidate + end + end + error("Не найдено поле " .. cache_key) +end + +emu.add_machine_reset_notifier(function() start_time = now() end) + +emu.register_periodic(function() + if not start_time then return end + if not ports then ports = manager.machine.ioport.ports end + local elapsed = now() - start_time + while index <= #events and elapsed >= events[index][1] do + local event = events[index] + field(event[2], event[3]):set_value(event[4]) + index = index + 1 + end + for _, snap_time in ipairs(snap_times) do + if not snaps_done[snap_time] and elapsed >= snap_time then + snaps_done[snap_time] = true + manager.machine.video:snapshot() + print("[p15-tree-self] snapshot t=" .. elapsed) + end + end + if elapsed >= 39.0 then + print("[p15-tree-self] exit t=" .. elapsed) + manager.machine:exit() + end +end) diff --git a/applications/Volkov/tests/mame_p16_group_tree_copy.lua b/applications/Volkov/tests/mame_p16_group_tree_copy.lua new file mode 100644 index 0000000..49a320a --- /dev/null +++ b/applications/Volkov/tests/mame_p16_group_tree_copy.lua @@ -0,0 +1,109 @@ +-- 0.2/P16: выбранный файл и два выбранных каталога копируются одним F5. + +local KB = ":kbd:ms_naturl:" +local HOLD = 0.06 +local events = {} + +local function add(t, tag, mask, value) + events[#events + 1] = {t, KB .. tag, mask, value} +end + +local function key(t, tag, mask) + add(t, tag, mask, 1) + add(t + HOLD, tag, mask, 0) +end + +local function chord(t, mod_tag, mod_mask, key_tag, key_mask) + add(t, mod_tag, mod_mask, 1) + add(t + 0.02, key_tag, key_mask, 1) + add(t + 0.08, key_tag, key_mask, 0) + add(t + 0.10, mod_tag, mod_mask, 0) +end + +local chars = { + c={"P1.4",0x08}, d={"P1.4",0x04}, e={"P1.4",0x20}, + m={"P2.0",0x10}, p={"P2.3",0x20}, r={"P1.5",0x01}, + s={"P1.2",0x08}, x={"P1.2",0x10}, ["."]={"P2.2",0x10}, + ["\n"]={"P2.1",0x10}, [":"]={"P2.3",0x02,true} +} + +local function type_text(t, value) + for i = 1, #value do + local ch = value:sub(i, i) + local def = chars[ch] or chars[ch:lower()] + local shifted = def[3] or ch ~= ch:lower() + if shifted then + chord(t, "P1.7", 0x02, def[1], def[2]) + else + key(t, def[1], def[2]) + end + t = t + 0.14 + end +end + +type_text(8.0, "d:\n") +type_text(10.0, "SPRCMD.EXE\n") +key(16.0, "P1.6", 0x20) -- right. +key(17.0, "P2.1", 0x10) -- enter GDST. +key(18.0, "P1.6", 0x20) -- left. +key(19.0, "P2.3", 0x10) -- GDST -> GSRC. +key(20.0, "P2.1", 0x10) -- enter GSRC. +key(21.0, "P2.3", 0x10) -- ADIR. +key(22.0, "P2.6", 0x01) -- select ADIR -> BDIR. +key(23.0, "P2.6", 0x01) -- select BDIR -> KEEP.TXT. +key(24.0, "P2.3", 0x10) -- KEEP.TXT -> ROOT.BIN. +key(25.0, "P2.6", 0x01) -- select ROOT.BIN. +key(28.0, "P2.1", 0x40) -- group F5. +key(39.0, "P1.6", 0x20) -- passive panel. +key(44.0, "P2.3", 0x80) -- F10. +key(46.0, "P2.1", 0x10) + +table.sort(events, function(left, right) return left[1] < right[1] end) + +local snap_times = {26.0, 36.0, 41.0, 49.0} +local snaps_done = {} +local start_time = nil +local ports = nil +local index = 1 +local field_cache = {} + +local function now() + local value = manager.machine.time + return value.seconds + value.attoseconds / 1e18 +end + +local function field(tag, mask) + local cache_key = tag .. "/" .. mask + if field_cache[cache_key] then return field_cache[cache_key] end + for _, candidate in pairs(ports[tag].fields) do + if candidate.mask == mask then + field_cache[cache_key] = candidate + return candidate + end + end + error("Не найдено поле " .. cache_key) +end + +emu.add_machine_reset_notifier(function() start_time = now() end) + +emu.register_periodic(function() + if not start_time then return end + if not ports then ports = manager.machine.ioport.ports end + local elapsed = now() - start_time + while index <= #events and elapsed >= events[index][1] do + local event = events[index] + field(event[2], event[3]):set_value(event[4]) + index = index + 1 + end + for _, snap_time in ipairs(snap_times) do + if not snaps_done[snap_time] and elapsed >= snap_time then + snaps_done[snap_time] = true + manager.machine.video:snapshot() + print("[p16-group-tree] snapshot t=" .. elapsed) + end + end + if elapsed >= 51.0 then + print("[p16-group-tree] exit t=" .. elapsed) + manager.machine:exit() + end +end) diff --git a/applications/Volkov/tests/mame_p17_tree_delete.lua b/applications/Volkov/tests/mame_p17_tree_delete.lua new file mode 100644 index 0000000..68cdea0 --- /dev/null +++ b/applications/Volkov/tests/mame_p17_tree_delete.lua @@ -0,0 +1,121 @@ +-- 0.2/P17: group F8 удаляет выбранные файлы и каталоги рекурсивно. + +local KB = ":kbd:ms_naturl:" +local HOLD = 0.06 +local events = {} + +local function add(t, tag, mask, value) + events[#events + 1] = {t, KB .. tag, mask, value} +end + +local function key(t, tag, mask) + add(t, tag, mask, 1) + add(t + HOLD, tag, mask, 0) +end + +local function chord(t, mod_tag, mod_mask, key_tag, key_mask) + add(t, mod_tag, mod_mask, 1) + add(t + 0.02, key_tag, key_mask, 1) + add(t + 0.08, key_tag, key_mask, 0) + add(t + 0.10, mod_tag, mod_mask, 0) +end + +local chars = { + c={"P1.4",0x08}, d={"P1.4",0x04}, e={"P1.4",0x20}, + m={"P2.0",0x10}, p={"P2.3",0x20}, r={"P1.5",0x01}, + s={"P1.2",0x08}, x={"P1.2",0x10}, ["."]={"P2.2",0x10}, + ["\n"]={"P2.1",0x10}, [":"]={"P2.3",0x02,true} +} + +local function type_text(t, value) + for i = 1, #value do + local ch = value:sub(i, i) + local def = chars[ch] or chars[ch:lower()] + local shifted = def[3] or ch ~= ch:lower() + if shifted then + chord(t, "P1.7", 0x02, def[1], def[2]) + else + key(t, def[1], def[2]) + end + t = t + 0.14 + end +end + +type_text(8.0, "d:\n") +type_text(10.0, "SPRCMD.EXE\n") + +key(17.0, "P2.1", 0x10) -- enter RDEL. +key(20.0, "P2.3", 0x10) -- `..` -> ADIR. +key(21.0, "P2.6", 0x01) -- select ADIR -> BDIR. +key(22.0, "P2.6", 0x01) -- select BDIR -> KEEPDIR. +key(23.0, "P2.3", 0x10) -- KEEPDIR -> KEEP.TXT. +key(24.0, "P2.3", 0x10) -- KEEP.TXT -> TOP.BIN. +key(25.0, "P2.6", 0x01) -- select TOP.BIN. + +key(27.0, "P2.2", 0x80) -- first group F8: cancel confirmation. +key(29.0, "P2.0", 0x08) -- N. +key(32.0, "P2.2", 0x80) -- second group F8. +key(34.0, "P2.1", 0x10) -- confirm. + +key(42.0, "P2.3", 0x80) -- F10. +key(44.0, "P2.1", 0x10) -- confirm exit. + +table.sort(events, function(left, right) return left[1] < right[1] end) + +local snap_times = { + 19.0, -- исходное дерево; + 25.5, -- два каталога и файл выбраны; + 27.5, -- подтверждение группы; + 30.0, -- cancel до mutation сохранил всё; + 39.0, -- выбранные деревья удалены; + 46.0 -- clean DSS prompt. +} +local snaps_done = {} +local start_time = nil +local ports = nil +local index = 1 +local field_cache = {} + +local function now() + local value = manager.machine.time + return value.seconds + value.attoseconds / 1e18 +end + +local function field(tag, mask) + local cache_key = tag .. "/" .. mask + if field_cache[cache_key] then return field_cache[cache_key] end + for _, candidate in pairs(ports[tag].fields) do + if candidate.mask == mask then + field_cache[cache_key] = candidate + return candidate + end + end + error("Не найдено поле " .. cache_key) +end + +emu.add_machine_reset_notifier(function() start_time = now() end) + +emu.register_periodic(function() + if not start_time then return end + if not ports then ports = manager.machine.ioport.ports end + local elapsed = now() - start_time + + while index <= #events and elapsed >= events[index][1] do + local event = events[index] + field(event[2], event[3]):set_value(event[4]) + index = index + 1 + end + + for _, snap_time in ipairs(snap_times) do + if not snaps_done[snap_time] and elapsed >= snap_time then + snaps_done[snap_time] = true + manager.machine.video:snapshot() + print("[p17-tree-delete] snapshot t=" .. elapsed) + end + end + + if elapsed >= 48.0 then + print("[p17-tree-delete] exit t=" .. elapsed) + manager.machine:exit() + end +end) diff --git a/applications/Volkov/tests/mame_p17_tree_delete_cancel.lua b/applications/Volkov/tests/mame_p17_tree_delete_cancel.lua new file mode 100644 index 0000000..e2fc3dc --- /dev/null +++ b/applications/Volkov/tests/mame_p17_tree_delete_cancel.lua @@ -0,0 +1,110 @@ +-- 0.2/P17: Esc во время рекурсивного group F8. + +local KB = ":kbd:ms_naturl:" +local HOLD = 0.06 +local events = {} + +local function add(t, tag, mask, value) + events[#events + 1] = {t, KB .. tag, mask, value} +end + +local function key(t, tag, mask) + add(t, tag, mask, 1) + add(t + HOLD, tag, mask, 0) +end + +local function chord(t, mod_tag, mod_mask, key_tag, key_mask) + add(t, mod_tag, mod_mask, 1) + add(t + 0.02, key_tag, key_mask, 1) + add(t + 0.08, key_tag, key_mask, 0) + add(t + 0.10, mod_tag, mod_mask, 0) +end + +local chars = { + c={"P1.4",0x08}, d={"P1.4",0x04}, e={"P1.4",0x20}, + m={"P2.0",0x10}, p={"P2.3",0x20}, r={"P1.5",0x01}, + s={"P1.2",0x08}, x={"P1.2",0x10}, ["."]={"P2.2",0x10}, + ["\n"]={"P2.1",0x10}, [":"]={"P2.3",0x02,true} +} + +local function type_text(t, value) + for i = 1, #value do + local ch = value:sub(i, i) + local def = chars[ch] or chars[ch:lower()] + local shifted = def[3] or ch ~= ch:lower() + if shifted then + chord(t, "P1.7", 0x02, def[1], def[2]) + else + key(t, def[1], def[2]) + end + t = t + 0.14 + end +end + +type_text(8.0, "d:\n") +type_text(10.0, "SPRCMD.EXE\n") + +key(17.0, "P2.1", 0x10) -- enter CDEL. +key(20.0, "P2.3", 0x10) -- `..` -> TREE. +key(21.0, "P2.6", 0x01) -- select TREE -> KEEP.TXT. +key(22.0, "P2.3", 0x10) -- KEEP.TXT -> TOP.BIN. +key(23.0, "P2.6", 0x01) -- select TOP.BIN. +key(25.0, "P2.2", 0x80) -- group F8. +key(27.0, "P2.1", 0x10) -- confirm; TOP.BIN удаляется первым. +key(27.5, "P1.6", 0x40) -- Esc во время enqueue большого дерева. + +key(35.0, "P2.3", 0x80) -- F10 после cleanup job. +key(37.0, "P2.1", 0x10) + +table.sort(events, function(left, right) return left[1] < right[1] end) + +local snap_times = {24.0, 25.5, 31.0, 39.0} +local snaps_done = {} +local start_time = nil +local ports = nil +local index = 1 +local field_cache = {} + +local function now() + local value = manager.machine.time + return value.seconds + value.attoseconds / 1e18 +end + +local function field(tag, mask) + local cache_key = tag .. "/" .. mask + if field_cache[cache_key] then return field_cache[cache_key] end + for _, candidate in pairs(ports[tag].fields) do + if candidate.mask == mask then + field_cache[cache_key] = candidate + return candidate + end + end + error("Не найдено поле " .. cache_key) +end + +emu.add_machine_reset_notifier(function() start_time = now() end) + +emu.register_periodic(function() + if not start_time then return end + if not ports then ports = manager.machine.ioport.ports end + local elapsed = now() - start_time + + while index <= #events and elapsed >= events[index][1] do + local event = events[index] + field(event[2], event[3]):set_value(event[4]) + index = index + 1 + end + + for _, snap_time in ipairs(snap_times) do + if not snaps_done[snap_time] and elapsed >= snap_time then + snaps_done[snap_time] = true + manager.machine.video:snapshot() + print("[p17-tree-delete-cancel] snapshot t=" .. elapsed) + end + end + + if elapsed >= 41.0 then + print("[p17-tree-delete-cancel] exit t=" .. elapsed) + manager.machine:exit() + end +end) diff --git a/applications/Volkov/tests/mame_p18_metadata.lua b/applications/Volkov/tests/mame_p18_metadata.lua new file mode 100644 index 0000000..50b405f --- /dev/null +++ b/applications/Volkov/tests/mame_p18_metadata.lua @@ -0,0 +1,109 @@ +-- 0.2/P18: F5 сохраняет точные FAT date/time и R/H/S/A. + +local KB = ":kbd:ms_naturl:" +local HOLD = 0.06 +local events = {} + +local function add(t, tag, mask, value) + events[#events + 1] = {t, KB .. tag, mask, value} +end + +local function key(t, tag, mask) + add(t, tag, mask, 1) + add(t + HOLD, tag, mask, 0) +end + +local function chord(t, mod_tag, mod_mask, key_tag, key_mask) + add(t, mod_tag, mod_mask, 1) + add(t + 0.02, key_tag, key_mask, 1) + add(t + 0.08, key_tag, key_mask, 0) + add(t + 0.10, mod_tag, mod_mask, 0) +end + +local chars = { + c={"P1.4",0x08}, d={"P1.4",0x04}, e={"P1.4",0x20}, + m={"P2.0",0x10}, p={"P2.3",0x20}, r={"P1.5",0x01}, + s={"P1.2",0x08}, x={"P1.2",0x10}, ["."]={"P2.2",0x10}, + ["\n"]={"P2.1",0x10}, [":"]={"P2.3",0x02,true} +} + +local function type_text(t, value) + for i = 1, #value do + local ch = value:sub(i, i) + local def = chars[ch] or chars[ch:lower()] + local shifted = def[3] or ch ~= ch:lower() + if shifted then + chord(t, "P1.7", 0x02, def[1], def[2]) + else + key(t, def[1], def[2]) + end + t = t + 0.14 + end +end + +type_text(8.0, "d:\n") +type_text(10.0, "SPRCMD.EXE\n") + +key(16.0, "P1.6", 0x20) -- right panel. +key(17.0, "P2.1", 0x10) -- enter MDST. +key(18.0, "P1.6", 0x20) -- left panel. +key(19.0, "P2.3", 0x10) -- MDST -> MSRC. +key(20.0, "P2.1", 0x10) -- enter MSRC. +key(22.0, "P2.3", 0x10) -- `..` -> META.BIN. +key(25.0, "P2.1", 0x40) -- F5. +key(31.0, "P1.6", 0x20) -- inspect refreshed MDST. +key(36.0, "P2.3", 0x80) -- F10. +key(38.0, "P2.1", 0x10) + +table.sort(events, function(left, right) return left[1] < right[1] end) + +local snap_times = {23.0, 29.0, 33.0, 40.0} +local snaps_done = {} +local start_time = nil +local ports = nil +local index = 1 +local field_cache = {} + +local function now() + local value = manager.machine.time + return value.seconds + value.attoseconds / 1e18 +end + +local function field(tag, mask) + local cache_key = tag .. "/" .. mask + if field_cache[cache_key] then return field_cache[cache_key] end + for _, candidate in pairs(ports[tag].fields) do + if candidate.mask == mask then + field_cache[cache_key] = candidate + return candidate + end + end + error("Не найдено поле " .. cache_key) +end + +emu.add_machine_reset_notifier(function() start_time = now() end) + +emu.register_periodic(function() + if not start_time then return end + if not ports then ports = manager.machine.ioport.ports end + local elapsed = now() - start_time + + while index <= #events and elapsed >= events[index][1] do + local event = events[index] + field(event[2], event[3]):set_value(event[4]) + index = index + 1 + end + + for _, snap_time in ipairs(snap_times) do + if not snaps_done[snap_time] and elapsed >= snap_time then + snaps_done[snap_time] = true + manager.machine.video:snapshot() + print("[p18-metadata] snapshot t=" .. elapsed) + end + end + + if elapsed >= 42.0 then + print("[p18-metadata] exit t=" .. elapsed) + manager.machine:exit() + end +end) diff --git a/applications/Volkov/tests/mame_p19_overwrite.lua b/applications/Volkov/tests/mame_p19_overwrite.lua new file mode 100644 index 0000000..8176f59 --- /dev/null +++ b/applications/Volkov/tests/mame_p19_overwrite.lua @@ -0,0 +1,87 @@ +-- P19: запускает target-пробу overwrite и фиксирует итоговую таблицу. + +local KB = ":kbd:ms_naturl:" +local HOLD = 0.06 +local events = {} + +local function add(t, tag, mask, value) + events[#events + 1] = {t, KB .. tag, mask, value} +end + +local function key(t, tag, mask) + add(t, tag, mask, 1) + add(t + HOLD, tag, mask, 0) +end + +local function chord(t, mod_tag, mod_mask, key_tag, key_mask) + add(t, mod_tag, mod_mask, 1) + add(t + 0.02, key_tag, key_mask, 1) + add(t + 0.08, key_tag, key_mask, 0) + add(t + 0.10, mod_tag, mod_mask, 0) +end + +local chars = { + d={"P1.4",0x04}, e={"P1.4",0x20}, l={"P2.2",0x08}, + r={"P1.5",0x01}, t={"P1.5",0x20}, x={"P1.2",0x10}, + ["."]={"P2.2",0x10}, + ["\n"]={"P2.1",0x10}, [":"]={"P2.3",0x02,true} +} + +local function type_text(t, value) + for i = 1, #value do + local ch = value:sub(i, i) + local def = chars[ch] or chars[ch:lower()] + local shifted = def[3] or ch ~= ch:lower() + if shifted then chord(t, "P1.7", 0x02, def[1], def[2]) + else key(t, def[1], def[2]) end + t = t + 0.14 + end +end + +type_text(8.0, "d:\n") +type_text(10.0, "DELRT.EXE\n") +key(18.0, "P2.1", 0x10) + +table.sort(events, function(a, b) return a[1] < b[1] end) +local snap_times = {15.0, 20.0} +local snaps_done = {} +local start_time = nil +local ports = nil +local index = 1 +local field_cache = {} + +local function now() + local value = manager.machine.time + return value.seconds + value.attoseconds / 1e18 +end + +local function field(tag, mask) + local cache_key = tag .. "/" .. mask + if field_cache[cache_key] then return field_cache[cache_key] end + for _, candidate in pairs(ports[tag].fields) do + if candidate.mask == mask then + field_cache[cache_key] = candidate + return candidate + end + end + error("Не найдено поле " .. cache_key) +end + +emu.add_machine_reset_notifier(function() start_time = now() end) +emu.register_periodic(function() + if not start_time then return end + if not ports then ports = manager.machine.ioport.ports end + local elapsed = now() - start_time + while index <= #events and elapsed >= events[index][1] do + local event = events[index] + field(event[2], event[3]):set_value(event[4]) + index = index + 1 + end + for _, snap_time in ipairs(snap_times) do + if not snaps_done[snap_time] and elapsed >= snap_time then + snaps_done[snap_time] = true + manager.machine.video:snapshot() + end + end + if elapsed >= 22.0 then manager.machine:exit() end +end) diff --git a/applications/Volkov/tests/mame_p19_policy.lua b/applications/Volkov/tests/mame_p19_policy.lua new file mode 100644 index 0000000..1886b7c --- /dev/null +++ b/applications/Volkov/tests/mame_p19_policy.lua @@ -0,0 +1,131 @@ +-- P19 UI: overwrite-all, readonly re-prompt, skip-all, rename и cancel. + +local KB = ":kbd:ms_naturl:" +local HOLD = 0.06 +local events = {} + +local function add(t, tag, mask, value) + events[#events + 1] = {t, KB .. tag, mask, value} +end + +local function key(t, tag, mask) + add(t, tag, mask, 1) + add(t + HOLD, tag, mask, 0) +end + +local function chord(t, mod_tag, mod_mask, key_tag, key_mask) + add(t, mod_tag, mod_mask, 1) + add(t + 0.02, key_tag, key_mask, 1) + add(t + 0.08, key_tag, key_mask, 0) + add(t + 0.10, mod_tag, mod_mask, 0) +end + +local chars = { + a={"P1.6",0x04}, b={"P1.5",0x10}, c={"P1.4",0x08}, + d={"P1.4",0x04}, e={"P1.4",0x20}, i={"P1.4",0x01}, + m={"P2.0",0x10}, n={"P2.0",0x08}, o={"P2.2",0x02}, + p={"P2.3",0x20}, r={"P1.5",0x01}, s={"P1.2",0x08}, + t={"P1.5",0x20}, x={"P1.2",0x10}, ["."]={"P2.2",0x10}, + ["\n"]={"P2.1",0x10}, [":"]={"P2.3",0x02,true} +} + +local function type_text(t, value) + for i = 1, #value do + local ch = value:sub(i, i) + local def = chars[ch] or chars[ch:lower()] + local shifted = def[3] or ch ~= ch:lower() + if shifted then chord(t, "P1.7", 0x02, def[1], def[2]) + else key(t, def[1], def[2]) end + t = t + 0.14 + end + return t +end + +type_text(8.0, "d:\n") +type_text(10.0, "SPRCMD.EXE\n") +key(16.0, "P1.6", 0x20) -- right root. +key(17.0, "P2.1", 0x10) -- enter P19DST. +key(18.0, "P1.6", 0x20) -- left root. +key(19.0, "P2.3", 0x10) -- P19DST -> P19SRC. +key(20.0, "P2.1", 0x10) -- enter P19SRC. + +key(22.0, "P2.3", 0x10) -- AONE. +key(23.0, "P2.6", 0x01) -- select AONE -> ATWO. +key(24.0, "P2.6", 0x01) -- select ATWO -> CANCEL. +key(25.0, "P2.3", 0x10) -- REN. +key(26.0, "P2.3", 0x10) -- RONLY. +key(27.0, "P2.6", 0x01) -- select RONLY -> SONE. +key(30.0, "P2.1", 0x40) -- group F5. +key(33.0, "P1.6", 0x04) -- A = overwrite all. +key(40.0, "P2.2", 0x02) -- O = explicit readonly overwrite. + +chord(47.0, "P1.1", 0x02, "P1.5", 0x01) -- refresh/reset selection. +key(49.0, "P2.6", 0x01) -- select SONE -> STWO. +key(50.0, "P2.6", 0x01) -- select STWO. +key(52.0, "P2.1", 0x40) -- group F5. +key(56.0, "P2.0", 0x08) -- N = skip all. + +chord(60.0, "P1.1", 0x02, "P1.5", 0x01) +key(62.0, "P2.5", 0x01) -- Home -> `..`. +key(63.0, "P2.3", 0x10) -- AONE. +key(64.0, "P2.3", 0x10) -- ATWO. +key(65.0, "P2.3", 0x10) -- CANCEL. +key(66.0, "P2.3", 0x10) -- REN. +key(68.0, "P2.1", 0x40) -- single F5. +key(72.0, "P1.5", 0x01) -- R = rename. +type_text(74.0, "NEXT.BIN\n") + +key(80.0, "P2.5", 0x01) -- Home -> `..`. +key(80.5, "P2.3", 0x10) -- AONE. +key(81.0, "P2.3", 0x10) -- ATWO. +key(81.5, "P2.3", 0x10) -- CANCEL. +key(83.0, "P2.1", 0x40) +key(87.0, "P1.6", 0x40) -- Esc conflict. +key(90.0, "P1.6", 0x20) -- inspect target. +key(95.0, "P2.3", 0x80) -- F10. +key(97.0, "P2.1", 0x10) + +table.sort(events, function(left, right) return left[1] < right[1] end) +local snap_times = {31.5, 38.0, 44.0, 54.0, 58.0, 70.0, 76.0, 85.0, 89.0, 92.0, 99.0} +local snaps_done = {} +local start_time = nil +local ports = nil +local index = 1 +local field_cache = {} + +local function now() + local value = manager.machine.time + return value.seconds + value.attoseconds / 1e18 +end + +local function field(tag, mask) + local cache_key = tag .. "/" .. mask + if field_cache[cache_key] then return field_cache[cache_key] end + for _, candidate in pairs(ports[tag].fields) do + if candidate.mask == mask then + field_cache[cache_key] = candidate + return candidate + end + end + error("Не найдено поле " .. cache_key) +end + +emu.add_machine_reset_notifier(function() start_time = now() end) +emu.register_periodic(function() + if not start_time then return end + if not ports then ports = manager.machine.ioport.ports end + local elapsed = now() - start_time + while index <= #events and elapsed >= events[index][1] do + local event = events[index] + field(event[2], event[3]):set_value(event[4]) + index = index + 1 + end + for _, snap_time in ipairs(snap_times) do + if not snaps_done[snap_time] and elapsed >= snap_time then + snaps_done[snap_time] = true + manager.machine.video:snapshot() + print("[p19-policy] snapshot t=" .. elapsed) + end + end + if elapsed >= 101.0 then manager.machine:exit() end +end) diff --git a/applications/Volkov/tests/mame_p2_skeleton.lua b/applications/Volkov/tests/mame_p2_skeleton.lua new file mode 100644 index 0000000..bb4d97e --- /dev/null +++ b/applications/Volkov/tests/mame_p2_skeleton.lua @@ -0,0 +1,130 @@ +-- Автоматический MAME-сценарий для двухпанельного skeleton P2. +-- Использует реальные поля AT-клавиатуры, включая PgDn и F1/F10, которых +-- нет в текстовом алфавите mame_interactive.py. + +local KB = ":kbd:ms_naturl:" +local HOLD = 0.06 +local events = {} + +local function add(t, tag, mask, value) + events[#events + 1] = {t, KB .. tag, mask, value} +end + +local function key(t, tag, mask) + add(t, tag, mask, 1) + add(t + HOLD, tag, mask, 0) +end + +local chars = { + a={"P1.6",0x04}, c={"P1.4",0x08}, d={"P1.4",0x04}, + e={"P1.4",0x20}, m={"P2.0",0x10}, n={"P2.0",0x08}, + p={"P2.3",0x20}, r={"P1.5",0x01}, s={"P1.2",0x08}, + x={"P1.2",0x10}, y={"P2.0",0x01}, + [" "]={"P2.4",0x80}, ["."]={"P2.2",0x10}, + ["\\"]={"P2.1",0x04}, ["\n"]={"P2.1",0x10}, + [":"]={"P2.3",0x02,true} +} + +local function chord(t, mod_tag, mod_mask, key_tag, key_mask) + add(t, mod_tag, mod_mask, 1) + add(t + 0.02, key_tag, key_mask, 1) + add(t + 0.08, key_tag, key_mask, 0) + add(t + 0.10, mod_tag, mod_mask, 0) +end + +local function type_text(t, value) + for i = 1, #value do + local ch = value:sub(i, i) + local def = chars[ch] + local shifted = false + if not def then + local lower = ch:lower() + def = chars[lower] + shifted = ch ~= lower + else + shifted = def[3] or false + end + if shifted then + chord(t, "P1.7", 0x02, def[1], def[2]) + else + key(t, def[1], def[2]) + end + t = t + 0.14 + end +end + +type_text(8.0, "a:\\SPRCMD .EXE\n") + +key(17.0, "P1.6", 0x20) -- Tab: активировать правую панель. +key(21.0, "P1.2", 0x20) -- PgDn: cursor 27, top 1, системный scroll. +key(22.0, "P2.3", 0x10) -- Down: cursor 28, top 2, ещё один scroll. +key(25.0, "P2.3", 0x80) -- F10: диалог выхода. +key(29.0, "P2.0", 0x08) -- N: отменить выход и восстановить экран. +key(33.0, "P1.2", 0x40) -- F1: единый диалог заглушки. +key(37.0, "P1.6", 0x40) -- Esc: закрыть сообщение. +key(40.0, "P2.3", 0x80) -- F10 повторно. +key(44.0, "P2.1", 0x10) -- Enter: подтвердить штатный выход. + +-- Немедленный второй запуск ловит утечку EMM между процессами. Сам EXE +-- дополнительно сравнивает free pages до alloc и после cleanup. +type_text(50.0, "a:\\SPRCMD .EXE\n") +key(59.0, "P2.3", 0x80) -- F10 во втором процессе. +key(63.0, "P2.1", 0x10) -- Подтвердить второй cleanup. + +table.sort(events, function(a, b) return a[1] < b[1] end) + +local snap_times = { + 15.0, 19.0, 23.0, 27.0, 31.0, 35.0, 39.0, 42.0, 47.0, + 57.0, 61.0, 66.0 +} +local snaps_done = {} +local start_time = nil +local ports = nil +local index = 1 +local field_cache = {} + +local function now() + local t = manager.machine.time + return t.seconds + t.attoseconds / 1e18 +end + +local function field(tag, mask) + local cache_key = tag .. "/" .. mask + if field_cache[cache_key] then return field_cache[cache_key] end + for _, candidate in pairs(ports[tag].fields) do + if candidate.mask == mask then + field_cache[cache_key] = candidate + return candidate + end + end + error("Не найдено поле " .. cache_key) +end + +emu.add_machine_reset_notifier(function() + start_time = now() +end) + +emu.register_periodic(function() + if not start_time then return end + if not ports then ports = manager.machine.ioport.ports end + local elapsed = now() - start_time + + while index <= #events and elapsed >= events[index][1] do + local event = events[index] + field(event[2], event[3]):set_value(event[4]) + index = index + 1 + end + + for _, snap_time in ipairs(snap_times) do + if not snaps_done[snap_time] and elapsed >= snap_time then + snaps_done[snap_time] = true + manager.machine.video:snapshot() + print("[p2-ui] snapshot t=" .. elapsed) + end + end + + if elapsed >= 68.0 then + print("[p2-ui] exit t=" .. elapsed) + manager.machine:exit() + end +end) diff --git a/applications/Volkov/tests/mame_p3_dir_probe.lua b/applications/Volkov/tests/mame_p3_dir_probe.lua new file mode 100644 index 0000000..f6c5ab2 --- /dev/null +++ b/applications/Volkov/tests/mame_p3_dir_probe.lua @@ -0,0 +1,104 @@ +-- Изолированная MAME-проба предельного каталога DSS. + +local KB = ":kbd:ms_naturl:" +local HOLD = 0.06 +local events = {} + +local function add(t, tag, mask, value) + events[#events + 1] = {t, KB .. tag, mask, value} +end + +local function key(t, tag, mask) + add(t, tag, mask, 1) + add(t + HOLD, tag, mask, 0) +end + +local function chord(t, mod_tag, mod_mask, key_tag, key_mask) + add(t, mod_tag, mod_mask, 1) + add(t + 0.02, key_tag, key_mask, 1) + add(t + 0.08, key_tag, key_mask, 0) + add(t + 0.10, mod_tag, mod_mask, 0) +end + +local chars = { + a={"P1.6",0x04}, d={"P1.4",0x04}, e={"P1.4",0x20}, + i={"P1.4",0x01}, p={"P2.3",0x20}, r={"P1.5",0x01}, + x={"P1.2",0x10}, ["3"]={"P1.4",0x40}, + [" "]={"P2.4",0x80}, ["."]={"P2.2",0x10}, + ["\\"]={"P2.1",0x04}, ["\n"]={"P2.1",0x10}, + [":"]={"P2.3",0x02,true} +} + +local function type_text(t, value) + for i = 1, #value do + local ch = value:sub(i, i) + local def = chars[ch] + local shifted = false + if not def then + local lower = ch:lower() + def = chars[lower] + shifted = ch ~= lower + else + shifted = def[3] or false + end + if shifted then + chord(t, "P1.7", 0x02, def[1], def[2]) + else + key(t, def[1], def[2]) + end + t = t + 0.14 + end +end + +type_text(8.0, "a:\\P3DIR .EXE\n") + +table.sort(events, function(a, b) return a[1] < b[1] end) + +local start_time = nil +local ports = nil +local index = 1 +local snapped = false +local field_cache = {} + +local function now() + local t = manager.machine.time + return t.seconds + t.attoseconds / 1e18 +end + +local function field(tag, mask) + local cache_key = tag .. "/" .. mask + if field_cache[cache_key] then return field_cache[cache_key] end + for _, candidate in pairs(ports[tag].fields) do + if candidate.mask == mask then + field_cache[cache_key] = candidate + return candidate + end + end + error("Не найдено поле " .. cache_key) +end + +emu.add_machine_reset_notifier(function() + start_time = now() +end) + +emu.register_periodic(function() + if not start_time then return end + if not ports then ports = manager.machine.ioport.ports end + local elapsed = now() - start_time + + while index <= #events and elapsed >= events[index][1] do + local event = events[index] + field(event[2], event[3]):set_value(event[4]) + index = index + 1 + end + + if not snapped and elapsed >= 18.0 then + snapped = true + manager.machine.video:snapshot() + print("[p3-dir-probe] snapshot t=" .. elapsed) + end + if elapsed >= 20.0 then + print("[p3-dir-probe] exit t=" .. elapsed) + manager.machine:exit() + end +end) diff --git a/applications/Volkov/tests/mame_p3_enum_matrix.lua b/applications/Volkov/tests/mame_p3_enum_matrix.lua new file mode 100644 index 0000000..a0e84f4 --- /dev/null +++ b/applications/Volkov/tests/mame_p3_enum_matrix.lua @@ -0,0 +1,98 @@ +-- Запуск P3MAP.EXE и снимок матрицы влияния W3 на F_NEXT. + +local KB = ":kbd:ms_naturl:" +local HOLD = 0.06 +local events = {} + +local function add(t, tag, mask, value) + events[#events + 1] = {t, KB .. tag, mask, value} +end + +local function key(t, tag, mask) + add(t, tag, mask, 1) + add(t + HOLD, tag, mask, 0) +end + +local function chord(t, mod_tag, mod_mask, key_tag, key_mask) + add(t, mod_tag, mod_mask, 1) + add(t + 0.02, key_tag, key_mask, 1) + add(t + 0.08, key_tag, key_mask, 0) + add(t + 0.10, mod_tag, mod_mask, 0) +end + +local chars = { + a={"P1.6",0x04}, e={"P1.4",0x20}, m={"P2.0",0x10}, + p={"P2.3",0x20}, x={"P1.2",0x10}, ["3"]={"P1.4",0x40}, + [" "]={"P2.4",0x80}, ["."]={"P2.2",0x10}, + ["\\"]={"P2.1",0x04}, ["\n"]={"P2.1",0x10}, + [":"]={"P2.3",0x02,true} +} + +local function type_text(t, value) + for i = 1, #value do + local ch = value:sub(i, i) + local def = chars[ch] + local shifted = false + if not def then + local lower = ch:lower() + def = chars[lower] + shifted = ch ~= lower + else + shifted = def[3] or false + end + if shifted then + chord(t, "P1.7", 0x02, def[1], def[2]) + else + key(t, def[1], def[2]) + end + t = t + 0.14 + end +end + +type_text(8.0, "a:\\P3MAP .EXE\n") +table.sort(events, function(a, b) return a[1] < b[1] end) + +local start_time = nil +local ports = nil +local index = 1 +local snapped = false +local field_cache = {} + +local function now() + local t = manager.machine.time + return t.seconds + t.attoseconds / 1e18 +end + +local function field(tag, mask) + local cache_key = tag .. "/" .. mask + if field_cache[cache_key] then return field_cache[cache_key] end + for _, candidate in pairs(ports[tag].fields) do + if candidate.mask == mask then + field_cache[cache_key] = candidate + return candidate + end + end + error("Не найдено поле " .. cache_key) +end + +emu.add_machine_reset_notifier(function() start_time = now() end) + +emu.register_periodic(function() + if not start_time then return end + if not ports then ports = manager.machine.ioport.ports end + local elapsed = now() - start_time + while index <= #events and elapsed >= events[index][1] do + local event = events[index] + field(event[2], event[3]):set_value(event[4]) + index = index + 1 + end + if not snapped and elapsed >= 30.0 then + snapped = true + manager.machine.video:snapshot() + print("[p3-enum-matrix] snapshot t=" .. elapsed) + end + if elapsed >= 32.0 then + print("[p3-enum-matrix] exit t=" .. elapsed) + manager.machine:exit() + end +end) diff --git a/applications/Volkov/tests/mame_p3_panels.lua b/applications/Volkov/tests/mame_p3_panels.lua new file mode 100644 index 0000000..566c93b --- /dev/null +++ b/applications/Volkov/tests/mame_p3_panels.lua @@ -0,0 +1,137 @@ +-- Автоматический MAME-сценарий P3: настоящие каталоги и две панели. + +local KB = ":kbd:ms_naturl:" +local HOLD = 0.06 +local events = {} + +local function add(t, tag, mask, value) + events[#events + 1] = {t, KB .. tag, mask, value} +end + +local function key(t, tag, mask) + add(t, tag, mask, 1) + add(t + HOLD, tag, mask, 0) +end + +local function chord(t, mod_tag, mod_mask, key_tag, key_mask) + add(t, mod_tag, mod_mask, 1) + add(t + 0.02, key_tag, key_mask, 1) + add(t + 0.08, key_tag, key_mask, 0) + add(t + 0.10, mod_tag, mod_mask, 0) +end + +local chars = { + a={"P1.6",0x04}, c={"P1.4",0x08}, d={"P1.4",0x04}, + e={"P1.4",0x20}, m={"P2.0",0x10}, p={"P2.3",0x20}, + r={"P1.5",0x01}, s={"P1.2",0x08}, x={"P1.2",0x10}, + [" "]={"P2.4",0x80}, ["."]={"P2.2",0x10}, + ["\\"]={"P2.1",0x04}, ["\n"]={"P2.1",0x10}, + [":"]={"P2.3",0x02,true} +} + +local function type_text(t, value) + for i = 1, #value do + local ch = value:sub(i, i) + local def = chars[ch] + local shifted = false + if not def then + local lower = ch:lower() + def = chars[lower] + shifted = ch ~= lower + else + shifted = def[3] or false + end + if shifted then + chord(t, "P1.7", 0x02, def[1], def[2]) + else + key(t, def[1], def[2]) + end + t = t + 0.14 + end +end + +type_text(8.0, "a:\\SPRCMD .EXE\n") + +key(17.0, "P2.1", 0x10) -- Enter: левая -> TESTDIR. +key(22.0, "P2.1", 0x10) -- Enter на `..`: обратно в корень. +key(27.0, "P2.1", 0x10) -- Снова открыть TESTDIR. +key(29.0, "P2.3", 0x10) -- Выбрать INNER.TXT. +chord(31.0, "P1.1", 0x02, "P1.5", 0x01) -- Ctrl+R: сохранить выбор имени. +key(36.0, "P1.6", 0x20) -- Tab: правая панель. +key(38.0, "P2.3", 0x10) -- Выбрать P3ROOT.TXT справа. +key(43.0, "P1.6", 0x20) -- Tab: вернуться в левую. +chord(45.0, "P1.1", 0x02, "P1.2", 0x01) -- Ctrl+PgUp: левая -> корень. +key(50.0, "P2.3", 0x80) -- F10. +key(55.0, "P2.1", 0x10) -- Enter: cleanup. + +-- Второй процесс проверяет освобождение EMM и повторную инициализацию темы. +type_text(61.0, "a:\\SPRCMD .EXE\n") +key(70.0, "P2.3", 0x80) +key(74.0, "P2.1", 0x10) + +table.sort(events, function(a, b) return a[1] < b[1] end) + +local snap_times = { + 15.0, -- root/root: TESTDIR должен быть первым; + 20.0, -- left TESTDIR: `..`, INNER.TXT; right root; + 25.0, -- Enter на `..` вернул left в root; + 34.0, -- refresh сохранил INNER.TXT и cursor=1; + 41.0, -- right root cursor=1, left TESTDIR cursor=1; + 48.0, -- Ctrl+PgUp изменил только left; + 53.0, -- quit dialog; + 58.0, -- DSS после первого cleanup; + 68.0, -- второй root/root; + 72.0, -- второй quit dialog; + 77.0 -- DSS после второго cleanup. +} +local snaps_done = {} +local start_time = nil +local ports = nil +local index = 1 +local field_cache = {} + +local function now() + local t = manager.machine.time + return t.seconds + t.attoseconds / 1e18 +end + +local function field(tag, mask) + local cache_key = tag .. "/" .. mask + if field_cache[cache_key] then return field_cache[cache_key] end + for _, candidate in pairs(ports[tag].fields) do + if candidate.mask == mask then + field_cache[cache_key] = candidate + return candidate + end + end + error("Не найдено поле " .. cache_key) +end + +emu.add_machine_reset_notifier(function() + start_time = now() +end) + +emu.register_periodic(function() + if not start_time then return end + if not ports then ports = manager.machine.ioport.ports end + local elapsed = now() - start_time + + while index <= #events and elapsed >= events[index][1] do + local event = events[index] + field(event[2], event[3]):set_value(event[4]) + index = index + 1 + end + + for _, snap_time in ipairs(snap_times) do + if not snaps_done[snap_time] and elapsed >= snap_time then + snaps_done[snap_time] = true + manager.machine.video:snapshot() + print("[p3-panels] snapshot t=" .. elapsed) + end + end + + if elapsed >= 79.0 then + print("[p3-panels] exit t=" .. elapsed) + manager.machine:exit() + end +end) diff --git a/applications/Volkov/tests/mame_p3_scan_probe.lua b/applications/Volkov/tests/mame_p3_scan_probe.lua new file mode 100644 index 0000000..f28015b --- /dev/null +++ b/applications/Volkov/tests/mame_p3_scan_probe.lua @@ -0,0 +1,107 @@ +-- Полная MAME-проба scan/store/sort большого каталога P3. + +local KB = ":kbd:ms_naturl:" +local HOLD = 0.06 +local events = {} + +local function add(t, tag, mask, value) + events[#events + 1] = {t, KB .. tag, mask, value} +end + +local function key(t, tag, mask) + add(t, tag, mask, 1) + add(t + HOLD, tag, mask, 0) +end + +local function chord(t, mod_tag, mod_mask, key_tag, key_mask) + add(t, mod_tag, mod_mask, 1) + add(t + 0.02, key_tag, key_mask, 1) + add(t + 0.08, key_tag, key_mask, 0) + add(t + 0.10, mod_tag, mod_mask, 0) +end + +local chars = { + a={"P1.6",0x04}, c={"P1.4",0x08}, d={"P1.4",0x04}, + e={"P1.4",0x20}, n={"P2.0",0x08}, p={"P2.3",0x20}, + s={"P1.2",0x08}, x={"P1.2",0x10}, + ["3"]={"P1.4",0x40}, [" "]={"P2.4",0x80}, + ["."]={"P2.2",0x10}, ["\\"]={"P2.1",0x04}, + ["\n"]={"P2.1",0x10}, [":"]={"P2.3",0x02,true} +} + +local function type_text(t, value) + for i = 1, #value do + local ch = value:sub(i, i) + local def = chars[ch] + local shifted = false + if not def then + local lower = ch:lower() + def = chars[lower] + shifted = ch ~= lower + else + shifted = def[3] or false + end + if shifted then + chord(t, "P1.7", 0x02, def[1], def[2]) + else + key(t, def[1], def[2]) + end + t = t + 0.14 + end +end + +type_text(8.0, "a:\\P3SCAN .EXE\n") + +table.sort(events, function(a, b) return a[1] < b[1] end) + +local snap_times = {20.0, 45.0, 75.0} +local snaps_done = {} +local start_time = nil +local ports = nil +local index = 1 +local field_cache = {} + +local function now() + local t = manager.machine.time + return t.seconds + t.attoseconds / 1e18 +end + +local function field(tag, mask) + local cache_key = tag .. "/" .. mask + if field_cache[cache_key] then return field_cache[cache_key] end + for _, candidate in pairs(ports[tag].fields) do + if candidate.mask == mask then + field_cache[cache_key] = candidate + return candidate + end + end + error("Не найдено поле " .. cache_key) +end + +emu.add_machine_reset_notifier(function() + start_time = now() +end) + +emu.register_periodic(function() + if not start_time then return end + if not ports then ports = manager.machine.ioport.ports end + local elapsed = now() - start_time + + while index <= #events and elapsed >= events[index][1] do + local event = events[index] + field(event[2], event[3]):set_value(event[4]) + index = index + 1 + end + + for _, snap_time in ipairs(snap_times) do + if not snaps_done[snap_time] and elapsed >= snap_time then + snaps_done[snap_time] = true + manager.machine.video:snapshot() + print("[p3-scan-probe] snapshot t=" .. elapsed) + end + end + if elapsed >= 78.0 then + print("[p3-scan-probe] exit t=" .. elapsed) + manager.machine:exit() + end +end) diff --git a/applications/Volkov/tests/mame_p3_stress.lua b/applications/Volkov/tests/mame_p3_stress.lua new file mode 100644 index 0000000..6997333 --- /dev/null +++ b/applications/Volkov/tests/mame_p3_stress.lua @@ -0,0 +1,137 @@ +-- P3 stress: пустой каталог, 40 строк и явный маркер предела DSS. + +local KB = ":kbd:ms_naturl:" +local HOLD = 0.06 +local events = {} + +local function add(t, tag, mask, value) + events[#events + 1] = {t, KB .. tag, mask, value} +end + +local function key(t, tag, mask) + add(t, tag, mask, 1) + add(t + HOLD, tag, mask, 0) +end + +local function chord(t, mod_tag, mod_mask, key_tag, key_mask) + add(t, mod_tag, mod_mask, 1) + add(t + 0.02, key_tag, key_mask, 1) + add(t + 0.08, key_tag, key_mask, 0) + add(t + 0.10, mod_tag, mod_mask, 0) +end + +local chars = { + c={"P1.4",0x08}, d={"P1.4",0x04}, + e={"P1.4",0x20}, m={"P2.0",0x10}, p={"P2.3",0x20}, + r={"P1.5",0x01}, s={"P1.2",0x08}, x={"P1.2",0x10}, + [" "]={"P2.4",0x80}, ["."]={"P2.2",0x10}, + ["\\"]={"P2.1",0x04}, ["\n"]={"P2.1",0x10}, + [":"]={"P2.3",0x02,true} +} + +local function type_text(t, value) + for i = 1, #value do + local ch = value:sub(i, i) + local def = chars[ch] + local shifted = false + if not def then + local lower = ch:lower() + def = chars[lower] + shifted = ch ~= lower + else + shifted = def[3] or false + end + if shifted then + chord(t, "P1.7", 0x02, def[1], def[2]) + else + key(t, def[1], def[2]) + end + t = t + 0.14 + end +end + +type_text(8.0, "d:\n") +type_text(9.0, "SPRCMD .EXE\n") + +-- Root order: BIGDIR, EMPTY, MANY, TESTDIR, P3ROOT, SPRCMD, ZETA. +key(17.0, "P2.3", 0x10) -- EMPTY. +key(18.0, "P2.1", 0x10) -- Enter EMPTY. +key(24.0, "P2.1", 0x10) -- Enter `..`. +key(27.0, "P2.3", 0x10) -- EMPTY. +key(28.0, "P2.3", 0x10) -- MANY. +key(29.0, "P2.1", 0x10) -- Enter MANY. +key(36.0, "P1.2", 0x20) -- PgDn -> cursor 27/top 1. +key(38.0, "P2.3", 0x10) -- Down -> cursor 28/top 2. +chord(43.0, "P1.1", 0x02, "P1.2", 0x01) -- Ctrl+PgUp -> root. +key(46.0, "P2.1", 0x10) -- Enter BIGDIR (index 0). + +-- BIGDIR scan может быть долгим; последующие события намеренно отнесены. +key(76.0, "P2.5", 0x20) -- End: красный `>>> MORE...`. +key(81.0, "P2.1", 0x10) -- Enter: пояснение, без перехода. +key(86.0, "P2.3", 0x80) -- F10. +key(90.0, "P2.1", 0x10) -- cleanup. + +table.sort(events, function(a, b) return a[1] < b[1] end) + +local snap_times = { + 15.0, -- root count=7, dirs before files; + 21.0, -- EMPTY: only synthetic `..`, count=1; + 32.0, -- MANY: count=41; + 40.0, -- real list cursor=28/top=2; + 73.0, -- BIGDIR: частичный список и признак усечения DSS; + 79.0, -- End: выбран красный виртуальный `>>> MORE...`; + 84.0, -- Enter: статус о недоступном остатке, каталог не меняется; + 88.0, -- quit dialog over intact screen; + 93.0 -- DSS after cleanup. +} +local snaps_done = {} +local start_time = nil +local ports = nil +local index = 1 +local field_cache = {} + +local function now() + local t = manager.machine.time + return t.seconds + t.attoseconds / 1e18 +end + +local function field(tag, mask) + local cache_key = tag .. "/" .. mask + if field_cache[cache_key] then return field_cache[cache_key] end + for _, candidate in pairs(ports[tag].fields) do + if candidate.mask == mask then + field_cache[cache_key] = candidate + return candidate + end + end + error("Не найдено поле " .. cache_key) +end + +emu.add_machine_reset_notifier(function() + start_time = now() +end) + +emu.register_periodic(function() + if not start_time then return end + if not ports then ports = manager.machine.ioport.ports end + local elapsed = now() - start_time + + while index <= #events and elapsed >= events[index][1] do + local event = events[index] + field(event[2], event[3]):set_value(event[4]) + index = index + 1 + end + + for _, snap_time in ipairs(snap_times) do + if not snaps_done[snap_time] and elapsed >= snap_time then + snaps_done[snap_time] = true + manager.machine.video:snapshot() + print("[p3-stress] snapshot t=" .. elapsed) + end + end + + if elapsed >= 95.0 then + print("[p3-stress] exit t=" .. elapsed) + manager.machine:exit() + end +end) diff --git a/applications/Volkov/tests/mame_p4_copy_hdd.lua b/applications/Volkov/tests/mame_p4_copy_hdd.lua new file mode 100644 index 0000000..30c8773 --- /dev/null +++ b/applications/Volkov/tests/mame_p4_copy_hdd.lua @@ -0,0 +1,142 @@ +-- P4/F5 на HDD D: успех >64 КБ, отказ overwrite и отмена с cleanup. + +local KB = ":kbd:ms_naturl:" +local HOLD = 0.06 +local events = {} + +local function add(t, tag, mask, value) + events[#events + 1] = {t, KB .. tag, mask, value} +end + +local function key(t, tag, mask) + add(t, tag, mask, 1) + add(t + HOLD, tag, mask, 0) +end + +local function chord(t, mod_tag, mod_mask, key_tag, key_mask) + add(t, mod_tag, mod_mask, 1) + add(t + 0.02, key_tag, key_mask, 1) + add(t + 0.08, key_tag, key_mask, 0) + add(t + 0.10, mod_tag, mod_mask, 0) +end + +local chars = { + c={"P1.4",0x08}, d={"P1.4",0x04}, e={"P1.4",0x20}, m={"P2.0",0x10}, + p={"P2.3",0x20}, r={"P1.5",0x01}, s={"P1.2",0x08}, + x={"P1.2",0x10}, [" "]={"P2.4",0x80}, ["."]={"P2.2",0x10}, + ["\n"]={"P2.1",0x10}, [":"]={"P2.3",0x02,true} +} + +local function type_text(t, value) + for i = 1, #value do + local ch = value:sub(i, i) + local def = chars[ch] + local shifted = false + if not def then + local lower = ch:lower() + def = chars[lower] + shifted = ch ~= lower + else + shifted = def[3] or false + end + if shifted then + chord(t, "P1.7", 0x02, def[1], def[2]) + else + key(t, def[1], def[2]) + end + t = t + 0.14 + end +end + +type_text(8.0, "d:\n") +type_text(9.0, "SPRCMD .EXE\n") + +key(15.0, "P2.1", 0x10) -- Enter: левая -> COPYFROM. +key(17.0, "P1.6", 0x20) -- Tab: правая панель. +key(18.0, "P2.3", 0x10) -- Down: COPYTO. +key(19.0, "P2.1", 0x10) -- Enter: правая -> COPYTO. +key(21.0, "P1.6", 0x20) -- Tab: левая панель. + +-- `..`, CANCEL, EMPTY, EXIST, F65535, F65536, K4095, K4096, LARGE. +for i = 1, 8 do key(22.0 + (i - 1) * 0.20, "P2.3", 0x10) end +key(25.0, "P2.1", 0x40) -- F5: LARGE.BIN (131109 байт). + +key(31.0, "P2.5", 0x01) -- Home. +for i = 1, 3 do key(32.0 + (i - 1) * 0.20, "P2.3", 0x10) end +key(33.5, "P2.1", 0x40) -- F5: EXIST.TXT, overwrite запрещён. + +key(36.0, "P2.5", 0x01) -- Home. +key(37.0, "P2.3", 0x10) -- CANCEL.BIN. +key(38.0, "P2.1", 0x40) -- F5: начать 4-МБ копирование. +key(41.0, "P1.6", 0x40) -- Esc: отменить между блоками. + +key(44.0, "P2.3", 0x10) -- Down: EMPTY.ZRO. +key(44.5, "P2.1", 0x40) -- F5: нулевой файл. + +key(48.0, "P2.3", 0x80) -- F10. +key(50.0, "P2.1", 0x10) -- Enter: cleanup и возврат в DSS. + +table.sort(events, function(a, b) return a[1] < b[1] end) + +local snap_times = { + 14.0, -- обе панели в корне D:; + 20.5, -- COPYFROM/COPYTO разведены по панелям; + 25.8, 28.5, 30.0, -- начало, поздний прогресс и результат LARGE.BIN; + 35.0, -- отказ перезаписи EXIST.TXT; + 39.5, -- активный прогресс CANCEL.BIN; + 43.0, -- отмена и cleanup temp; + 46.0, -- нулевой EMPTY.ZRO скопирован; + 49.0, -- диалог выхода; + 52.0 -- возврат в D:\>. +} +local snaps_done = {} +local start_time = nil +local ports = nil +local index = 1 +local field_cache = {} + +local function now() + local t = manager.machine.time + return t.seconds + t.attoseconds / 1e18 +end + +local function field(tag, mask) + local cache_key = tag .. "/" .. mask + if field_cache[cache_key] then return field_cache[cache_key] end + for _, candidate in pairs(ports[tag].fields) do + if candidate.mask == mask then + field_cache[cache_key] = candidate + return candidate + end + end + error("Не найдено поле " .. cache_key) +end + +emu.add_machine_reset_notifier(function() + start_time = now() +end) + +emu.register_periodic(function() + if not start_time then return end + if not ports then ports = manager.machine.ioport.ports end + local elapsed = now() - start_time + + while index <= #events and elapsed >= events[index][1] do + local event = events[index] + field(event[2], event[3]):set_value(event[4]) + index = index + 1 + end + + for _, snap_time in ipairs(snap_times) do + if not snaps_done[snap_time] and elapsed >= snap_time then + snaps_done[snap_time] = true + manager.machine.video:snapshot() + print("[p4-copy-hdd] snapshot t=" .. elapsed) + end + end + + if elapsed >= 54.0 then + print("[p4-copy-hdd] exit t=" .. elapsed) + manager.machine:exit() + end +end) diff --git a/applications/Volkov/tests/mame_p4_exec_hdd.lua b/applications/Volkov/tests/mame_p4_exec_hdd.lua new file mode 100644 index 0000000..7d98d2e --- /dev/null +++ b/applications/Volkov/tests/mame_p4_exec_hdd.lua @@ -0,0 +1,141 @@ +-- P4.2 на HDD D: child меняет mode/palette/CWD и обе панели. + +local KB = ":kbd:ms_naturl:" +local HOLD = 0.06 +local events = {} + +local function add(t, tag, mask, value) + events[#events + 1] = {t, KB .. tag, mask, value} +end + +local function key(t, tag, mask) + add(t, tag, mask, 1) + add(t + HOLD, tag, mask, 0) +end + +local function chord(t, mod_tag, mod_mask, key_tag, key_mask) + add(t, mod_tag, mod_mask, 1) + add(t + 0.02, key_tag, key_mask, 1) + add(t + 0.08, key_tag, key_mask, 0) + add(t + 0.10, mod_tag, mod_mask, 0) +end + +local chars = { + c={"P1.4",0x08}, d={"P1.4",0x04}, e={"P1.4",0x20}, + m={"P2.0",0x10}, p={"P2.3",0x20}, r={"P1.5",0x01}, + s={"P1.2",0x08}, x={"P1.2",0x10}, [" "]={"P2.4",0x80}, + ["."]={"P2.2",0x10}, ["\n"]={"P2.1",0x10}, + [":"]={"P2.3",0x02,true} +} + +local function type_text(t, value) + for i = 1, #value do + local ch = value:sub(i, i) + local def = chars[ch] + local shifted = false + if not def then + local lower = ch:lower() + def = chars[lower] + shifted = ch ~= lower + else + shifted = def[3] or false + end + if shifted then + chord(t, "P1.7", 0x02, def[1], def[2]) + else + key(t, def[1], def[2]) + end + t = t + 0.14 + end +end + +type_text(8.0, "d:\n") +type_text(9.0, "SPRCMD .EXE\n") + +key(15.0, "P2.1", 0x10) -- левая -> EXECLEFT. +key(17.0, "P1.6", 0x20) -- Tab. +key(18.0, "P2.3", 0x10) -- EXECRGHT. +key(19.0, "P2.1", 0x10) -- правая -> EXECRGHT. +key(21.0, "P1.6", 0x20) -- Tab влево. + +-- `..`, BROKEN.EXE, NOTE.TXT, P4CHILD.EXE. +for i = 1, 3 do key(22.0 + (i - 1) * 0.20, "P2.3", 0x10) end +key(23.5, "P2.1", 0x10) -- Enter: P4CHILD.EXE. +key(28.0, "P2.1", 0x10) -- Enter внутри child: return 5A. + +-- После refresh: `..`, BROKEN, CREATED, NOTE, P4CHILD. +key(34.0, "P2.5", 0x01) -- Home. +key(35.0, "P2.3", 0x10) -- BROKEN.EXE. +key(36.0, "P2.1", 0x10) -- ошибка загрузчика EXE. + +key(40.0, "P2.5", 0x01) -- Home. +for i = 1, 3 do key(41.0 + (i - 1) * 0.20, "P2.3", 0x10) end +key(42.5, "P2.1", 0x10) -- NOTE.TXT: тип не поддерживается. + +key(47.0, "P2.3", 0x80) -- F10. +key(49.0, "P2.1", 0x10) -- cleanup. + +table.sort(events, function(a, b) return a[1] < b[1] end) + +local snap_times = { + 14.0, -- root D:; + 20.5, -- панели EXECLEFT/EXECRIGHT; + 26.0, -- экран дочерней программы 40x32; + 32.0, -- Commander восстановлен, обе панели refreshed; + 38.0, -- ошибка BROKEN.EXE без завершения Commander; + 45.0, -- NOTE.TXT не запускается; + 48.0, -- quit dialog поверх восстановленного экрана; + 51.0 -- чистый возврат в D:\>. +} +local snaps_done = {} +local start_time = nil +local ports = nil +local index = 1 +local field_cache = {} + +local function now() + local t = manager.machine.time + return t.seconds + t.attoseconds / 1e18 +end + +local function field(tag, mask) + local cache_key = tag .. "/" .. mask + if field_cache[cache_key] then return field_cache[cache_key] end + for _, candidate in pairs(ports[tag].fields) do + if candidate.mask == mask then + field_cache[cache_key] = candidate + return candidate + end + end + error("Не найдено поле " .. cache_key) +end + + +emu.add_machine_reset_notifier(function() + start_time = now() +end) + +emu.register_periodic(function() + if not start_time then return end + if not ports then ports = manager.machine.ioport.ports end + local elapsed = now() - start_time + + while index <= #events and elapsed >= events[index][1] do + local event = events[index] + field(event[2], event[3]):set_value(event[4]) + index = index + 1 + end + + for _, snap_time in ipairs(snap_times) do + if not snaps_done[snap_time] and elapsed >= snap_time then + snaps_done[snap_time] = true + manager.machine.video:snapshot() + print("[p4-exec-hdd] snapshot t=" .. elapsed) + end + end + + if elapsed >= 53.0 then + print("[p4-exec-hdd] exit t=" .. elapsed) + manager.machine:exit() + end +end) diff --git a/applications/Volkov/tests/mame_p5_copy_faults.lua b/applications/Volkov/tests/mame_p5_copy_faults.lua new file mode 100644 index 0000000..48a68f9 --- /dev/null +++ b/applications/Volkov/tests/mame_p5_copy_faults.lua @@ -0,0 +1,107 @@ +-- P5: отдельная детерминированная матрица состояний copy-job на HDD D:. + +local KB = ":kbd:ms_naturl:" +local HOLD = 0.06 +local events = {} + +local function add(t, tag, mask, value) + events[#events + 1] = {t, KB .. tag, mask, value} +end + +local function key(t, tag, mask) + add(t, tag, mask, 1) + add(t + HOLD, tag, mask, 0) +end + +local function chord(t, mod_tag, mod_mask, key_tag, key_mask) + add(t, mod_tag, mod_mask, 1) + add(t + 0.02, key_tag, key_mask, 1) + add(t + 0.08, key_tag, key_mask, 0) + add(t + 0.10, mod_tag, mod_mask, 0) +end + +local chars = { + c={"P1.4",0x08}, d={"P1.4",0x04}, e={"P1.4",0x20}, + p={"P2.3",0x20}, r={"P1.5",0x01}, x={"P1.2",0x10}, + ["."]={"P2.2",0x10}, ["\n"]={"P2.1",0x10}, [":"]={"P2.3",0x02,true} +} + +local function type_text(t, value) + for i = 1, #value do + local ch = value:sub(i, i) + local def = chars[ch] + local shifted = false + if not def then + local lower = ch:lower() + def = chars[lower] + shifted = ch ~= lower + else + shifted = def[3] or false + end + if shifted then + chord(t, "P1.7", 0x02, def[1], def[2]) + else + key(t, def[1], def[2]) + end + t = t + 0.14 + end +end + +type_text(8.0, "d:\n") +type_text(9.0, "CPERR.EXE\n") +key(16.0, "P2.1", 0x10) -- Enter после итоговой таблицы. + +table.sort(events, function(a, b) return a[1] < b[1] end) + +local snap_times = {14.0, 18.0} +local snaps_done = {} +local start_time = nil +local ports = nil +local index = 1 +local field_cache = {} + +local function now() + local t = manager.machine.time + return t.seconds + t.attoseconds / 1e18 +end + +local function field(tag, mask) + local cache_key = tag .. "/" .. mask + if field_cache[cache_key] then return field_cache[cache_key] end + for _, candidate in pairs(ports[tag].fields) do + if candidate.mask == mask then + field_cache[cache_key] = candidate + return candidate + end + end + error("Не найдено поле " .. cache_key) +end + +emu.add_machine_reset_notifier(function() + start_time = now() +end) + +emu.register_periodic(function() + if not start_time then return end + if not ports then ports = manager.machine.ioport.ports end + local elapsed = now() - start_time + + while index <= #events and elapsed >= events[index][1] do + local event = events[index] + field(event[2], event[3]):set_value(event[4]) + index = index + 1 + end + + for _, snap_time in ipairs(snap_times) do + if not snaps_done[snap_time] and elapsed >= snap_time then + snaps_done[snap_time] = true + manager.machine.video:snapshot() + print("[p5-copy-faults] snapshot t=" .. elapsed) + end + end + + if elapsed >= 20.0 then + print("[p5-copy-faults] exit t=" .. elapsed) + manager.machine:exit() + end +end) diff --git a/applications/Volkov/tests/mame_p5_enospc.lua b/applications/Volkov/tests/mame_p5_enospc.lua new file mode 100644 index 0000000..905116a --- /dev/null +++ b/applications/Volkov/tests/mame_p5_enospc.lua @@ -0,0 +1,125 @@ +-- P5 на HDD D: реальный disk-full при F5 и обязательный cleanup temp. + +local KB = ":kbd:ms_naturl:" +local HOLD = 0.06 +local events = {} + +local function add(t, tag, mask, value) + events[#events + 1] = {t, KB .. tag, mask, value} +end + +local function key(t, tag, mask) + add(t, tag, mask, 1) + add(t + HOLD, tag, mask, 0) +end + +local function chord(t, mod_tag, mod_mask, key_tag, key_mask) + add(t, mod_tag, mod_mask, 1) + add(t + 0.02, key_tag, key_mask, 1) + add(t + 0.08, key_tag, key_mask, 0) + add(t + 0.10, mod_tag, mod_mask, 0) +end + +local chars = { + c={"P1.4",0x08}, d={"P1.4",0x04}, e={"P1.4",0x20}, + m={"P2.0",0x10}, p={"P2.3",0x20}, r={"P1.5",0x01}, + s={"P1.2",0x08}, x={"P1.2",0x10}, [" "]={"P2.4",0x80}, + ["."]={"P2.2",0x10}, ["\n"]={"P2.1",0x10}, + [":"]={"P2.3",0x02,true} +} + +local function type_text(t, value) + for i = 1, #value do + local ch = value:sub(i, i) + local def = chars[ch] + local shifted = false + if not def then + local lower = ch:lower() + def = chars[lower] + shifted = ch ~= lower + else + shifted = def[3] or false + end + if shifted then + chord(t, "P1.7", 0x02, def[1], def[2]) + else + key(t, def[1], def[2]) + end + t = t + 0.14 + end +end + +type_text(8.0, "d:\n") +type_text(9.0, "SPRCMD .EXE\n") + +key(15.0, "P2.1", 0x10) -- левая -> SOURCE. +key(17.0, "P1.6", 0x20) -- Tab. +key(18.0, "P2.3", 0x10) -- TARGET. +key(19.0, "P2.1", 0x10) -- правая -> TARGET. +key(21.0, "P1.6", 0x20) -- Tab влево. +key(22.0, "P2.3", 0x10) -- FULL.BIN после `..`. +key(23.0, "P2.1", 0x40) -- F5, ожидается ENOSPC. + +key(35.0, "P2.3", 0x80) -- F10 после управляемой ошибки. +key(37.0, "P2.1", 0x10) + +table.sort(events, function(a, b) return a[1] < b[1] end) + +local snap_times = { + 20.5, -- SOURCE/TARGET на почти полном диске; + 25.0, -- промежуточный progress либо уже ошибка; + 32.0, -- управляемый ENOSPC, TARGET пуст; + 36.0, -- диалог выхода после ошибки; + 40.0 -- чистый prompt DSS. +} +local snaps_done = {} +local start_time = nil +local ports = nil +local index = 1 +local field_cache = {} + +local function now() + local t = manager.machine.time + return t.seconds + t.attoseconds / 1e18 +end + +local function field(tag, mask) + local cache_key = tag .. "/" .. mask + if field_cache[cache_key] then return field_cache[cache_key] end + for _, candidate in pairs(ports[tag].fields) do + if candidate.mask == mask then + field_cache[cache_key] = candidate + return candidate + end + end + error("Не найдено поле " .. cache_key) +end + +emu.add_machine_reset_notifier(function() + start_time = now() +end) + +emu.register_periodic(function() + if not start_time then return end + if not ports then ports = manager.machine.ioport.ports end + local elapsed = now() - start_time + + while index <= #events and elapsed >= events[index][1] do + local event = events[index] + field(event[2], event[3]):set_value(event[4]) + index = index + 1 + end + + for _, snap_time in ipairs(snap_times) do + if not snaps_done[snap_time] and elapsed >= snap_time then + snaps_done[snap_time] = true + manager.machine.video:snapshot() + print("[p5-enospc] snapshot t=" .. elapsed) + end + end + + if elapsed >= 42.0 then + print("[p5-enospc] exit t=" .. elapsed) + manager.machine:exit() + end +end) diff --git a/applications/Volkov/tests/mame_p5_media_change.lua b/applications/Volkov/tests/mame_p5_media_change.lua new file mode 100644 index 0000000..60d190d --- /dev/null +++ b/applications/Volkov/tests/mame_p5_media_change.lua @@ -0,0 +1,133 @@ +-- P5: извлечение и возврат тестового HDD D: во время работы Commander. + +local KB = ":kbd:ms_naturl:" +local HOLD = 0.06 +local HDD_TAG = ":ata2:1:hdd:image" +local events = {} + +local function add(t, tag, mask, value) + events[#events + 1] = {t, KB .. tag, mask, value} +end + +local function key(t, tag, mask) + add(t, tag, mask, 1) + add(t + HOLD, tag, mask, 0) +end + +local function chord(t, mod_tag, mod_mask, key_tag, key_mask) + add(t, mod_tag, mod_mask, 1) + add(t + 0.02, key_tag, key_mask, 1) + add(t + 0.08, key_tag, key_mask, 0) + add(t + 0.10, mod_tag, mod_mask, 0) +end + +local chars = { + c={"P1.4",0x08}, d={"P1.4",0x04}, e={"P1.4",0x20}, + m={"P2.0",0x10}, p={"P2.3",0x20}, r={"P1.5",0x01}, + s={"P1.2",0x08}, x={"P1.2",0x10}, [" "]={"P2.4",0x80}, + ["."]={"P2.2",0x10}, ["\n"]={"P2.1",0x10}, + [":"]={"P2.3",0x02,true} +} + +local function type_text(t, value) + for i = 1, #value do + local ch = value:sub(i, i) + local def = chars[ch] + local shifted = false + if not def then + local lower = ch:lower() + def = chars[lower] + shifted = ch ~= lower + else + shifted = def[3] or false + end + if shifted then + chord(t, "P1.7", 0x02, def[1], def[2]) + else + key(t, def[1], def[2]) + end + t = t + 0.14 + end +end + +type_text(8.0, "d:\n") +type_text(9.0, "SPRCMD .EXE\n") + +key(15.0, "P2.1", 0x10) -- левая панель -> MEDIA. +key(17.0, "P2.3", 0x10) -- DEEP. +key(17.3, "P2.3", 0x10) -- DATA.BIN. +key(19.0, "P2.1", 0x40) -- F5 при отсутствующем HDD. +key(26.0, "P2.1", 0x40) -- тот же F5 после возврата HDD. +key(34.0, "P2.3", 0x80) -- F10. +key(36.0, "P2.1", 0x10) -- подтвердить выход. + +table.sort(events, function(a, b) return a[1] < b[1] end) + +local snap_times = {14.0, 17.7, 21.0, 24.5, 31.0, 35.0, 38.0} +local snaps_done = {} +local start_time = nil +local ports = nil +local index = 1 +local field_cache = {} +local image_path = nil +local media_phase = 0 + +local function now() + local t = manager.machine.time + return t.seconds + t.attoseconds / 1e18 +end + +local function field(tag, mask) + local cache_key = tag .. "/" .. mask + if field_cache[cache_key] then return field_cache[cache_key] end + for _, candidate in pairs(ports[tag].fields) do + if candidate.mask == mask then + field_cache[cache_key] = candidate + return candidate + end + end + error("Не найдено поле " .. cache_key) +end + +emu.add_machine_reset_notifier(function() + start_time = now() +end) + +emu.register_periodic(function() + if not start_time then return end + if not ports then ports = manager.machine.ioport.ports end + local elapsed = now() - start_time + + if media_phase == 0 and elapsed >= 18.0 then + local hdd = assert(manager.machine.images[HDD_TAG], "hard2 image not found") + image_path = hdd.filename + hdd:unload() + media_phase = 1 + print("[p5-media] hard2 unloaded, exists=" .. tostring(hdd.exists)) + elseif media_phase == 1 and elapsed >= 23.0 then + local hdd = assert(manager.machine.images[HDD_TAG], "hard2 image not found") + local err = hdd:load(image_path) + media_phase = 2 + print("[p5-media] hard2 loaded, exists=" .. tostring(hdd.exists) .. + " err=" .. tostring(err)) + end + + while index <= #events and elapsed >= events[index][1] do + local event = events[index] + field(event[2], event[3]):set_value(event[4]) + index = index + 1 + end + + for _, snap_time in ipairs(snap_times) do + if not snaps_done[snap_time] and elapsed >= snap_time then + snaps_done[snap_time] = true + manager.machine.video:snapshot() + print("[p5-media] snapshot t=" .. elapsed) + end + end + + if elapsed >= 40.0 then + print("[p5-media] exit t=" .. elapsed) + manager.machine:exit() + end +end) diff --git a/applications/Volkov/tests/mame_p5_panel_edges.lua b/applications/Volkov/tests/mame_p5_panel_edges.lua new file mode 100644 index 0000000..951d2a8 --- /dev/null +++ b/applications/Volkov/tests/mame_p5_panel_edges.lua @@ -0,0 +1,124 @@ +-- P5: ровно 27/28 записей и начало прокрутки на 28-й. + +local KB = ":kbd:ms_naturl:" +local HOLD = 0.06 +local events = {} + +local function add(t, tag, mask, value) + events[#events + 1] = {t, KB .. tag, mask, value} +end + +local function key(t, tag, mask) + add(t, tag, mask, 1) + add(t + HOLD, tag, mask, 0) +end + +local function chord(t, mod_tag, mod_mask, key_tag, key_mask) + add(t, mod_tag, mod_mask, 1) + add(t + 0.02, key_tag, key_mask, 1) + add(t + 0.08, key_tag, key_mask, 0) + add(t + 0.10, mod_tag, mod_mask, 0) +end + +local chars = { + c={"P1.4",0x08}, d={"P1.4",0x04}, e={"P1.4",0x20}, + m={"P2.0",0x10}, p={"P2.3",0x20}, r={"P1.5",0x01}, + s={"P1.2",0x08}, x={"P1.2",0x10}, [" "]={"P2.4",0x80}, + ["."]={"P2.2",0x10}, ["\n"]={"P2.1",0x10}, + [":"]={"P2.3",0x02,true} +} + +local function type_text(t, value) + for i = 1, #value do + local ch = value:sub(i, i) + local def = chars[ch] + local shifted = false + if not def then + local lower = ch:lower() + def = chars[lower] + shifted = ch ~= lower + else + shifted = def[3] or false + end + if shifted then + chord(t, "P1.7", 0x02, def[1], def[2]) + else + key(t, def[1], def[2]) + end + t = t + 0.14 + end +end + +type_text(8.0, "d:\n") +type_text(9.0, "SPRCMD .EXE\n") + +key(15.0, "P2.1", 0x10) -- левая -> N27. +key(17.0, "P1.6", 0x20) -- Tab. +key(18.0, "P2.3", 0x10) -- N28. +key(19.0, "P2.1", 0x10) -- правая -> N28. +key(23.0, "P2.5", 0x20) -- End: cursor=27, top=1. +key(27.0, "P1.6", 0x20) -- Tab: N27. +key(28.0, "P2.5", 0x20) -- End: cursor=26, top=0. +key(32.0, "P2.3", 0x80) -- F10. +key(34.0, "P2.1", 0x10) + +table.sort(events, function(a, b) return a[1] < b[1] end) + +local snap_times = { + 20.5, -- count 27/28, обе панели top=0; + 25.0, -- N28 cursor=27/top=1; + 30.0, -- N27 cursor=26/top=0; + 36.0 -- чистый prompt DSS. +} +local snaps_done = {} +local start_time = nil +local ports = nil +local index = 1 +local field_cache = {} + +local function now() + local t = manager.machine.time + return t.seconds + t.attoseconds / 1e18 +end + +local function field(tag, mask) + local cache_key = tag .. "/" .. mask + if field_cache[cache_key] then return field_cache[cache_key] end + for _, candidate in pairs(ports[tag].fields) do + if candidate.mask == mask then + field_cache[cache_key] = candidate + return candidate + end + end + error("Не найдено поле " .. cache_key) +end + + +emu.add_machine_reset_notifier(function() + start_time = now() +end) + +emu.register_periodic(function() + if not start_time then return end + if not ports then ports = manager.machine.ioport.ports end + local elapsed = now() - start_time + + while index <= #events and elapsed >= events[index][1] do + local event = events[index] + field(event[2], event[3]):set_value(event[4]) + index = index + 1 + end + + for _, snap_time in ipairs(snap_times) do + if not snaps_done[snap_time] and elapsed >= snap_time then + snaps_done[snap_time] = true + manager.machine.video:snapshot() + print("[p5-panel-edges] snapshot t=" .. elapsed) + end + end + + if elapsed >= 38.0 then + print("[p5-panel-edges] exit t=" .. elapsed) + manager.machine:exit() + end +end) diff --git a/applications/Volkov/tests/mame_p5_stability.lua b/applications/Volkov/tests/mame_p5_stability.lua new file mode 100644 index 0000000..7bed2d2 --- /dev/null +++ b/applications/Volkov/tests/mame_p5_stability.lua @@ -0,0 +1,159 @@ +-- P5 на HDD D: 20 copy, 100 refresh и три полных cleanup-цикла. + +local KB = ":kbd:ms_naturl:" +local HOLD = 0.06 +local events = {} + +local function add(t, tag, mask, value) + events[#events + 1] = {t, KB .. tag, mask, value} +end + +local function key(t, tag, mask) + add(t, tag, mask, 1) + add(t + HOLD, tag, mask, 0) +end + +local function chord(t, mod_tag, mod_mask, key_tag, key_mask) + add(t, mod_tag, mod_mask, 1) + add(t + 0.02, key_tag, key_mask, 1) + add(t + 0.08, key_tag, key_mask, 0) + add(t + 0.10, mod_tag, mod_mask, 0) +end + +local function refresh_chord(t) + add(t, "P1.1", 0x02, 1) + add(t + 0.03, "P1.5", 0x01, 1) + add(t + 0.20, "P1.5", 0x01, 0) + add(t + 0.23, "P1.1", 0x02, 0) +end + +local chars = { + c={"P1.4",0x08}, d={"P1.4",0x04}, e={"P1.4",0x20}, + m={"P2.0",0x10}, p={"P2.3",0x20}, r={"P1.5",0x01}, + s={"P1.2",0x08}, x={"P1.2",0x10}, [" "]={"P2.4",0x80}, + ["."]={"P2.2",0x10}, ["\n"]={"P2.1",0x10}, + [":"]={"P2.3",0x02,true} +} + +local function type_text(t, value) + for i = 1, #value do + local ch = value:sub(i, i) + local def = chars[ch] + local shifted = false + if not def then + local lower = ch:lower() + def = chars[lower] + shifted = ch ~= lower + else + shifted = def[3] or false + end + if shifted then + chord(t, "P1.7", 0x02, def[1], def[2]) + else + key(t, def[1], def[2]) + end + t = t + 0.14 + end +end + +type_text(8.0, "d:\n") +type_text(9.0, "SPRCMD .EXE\n") + +key(15.0, "P2.1", 0x10) -- левая -> SOURCE. +key(17.0, "P1.6", 0x20) -- Tab. +key(18.0, "P2.3", 0x10) -- TARGET. +key(19.0, "P2.1", 0x10) -- правая -> TARGET. +key(21.0, "P1.6", 0x20) -- Tab влево. + +-- В SOURCE: `..`, затем F00.BIN..F19.BIN. Copy не меняет active selection. +-- Интервалы растут вместе с числом 4-КБ блоков: ввод во время синхронного +-- copy не буферизуется и не должен считаться выполненной командой. +local copy_times = { + 22.0, 24.0, 26.0, 28.0, 30.0, 34.0, 38.0, 40.0, 42.0, 44.0, + 46.0, 48.0, 50.0, 52.0, 54.0, 57.0, 60.0, 64.0, 68.0, 75.0 +} +for i = 0, 19 do + local t = copy_times[i + 1] + key(t, "P2.3", 0x10) -- Down. + key(t + 0.25, "P2.1", 0x40) -- F5. +end + +-- Последний файл больше 1 МБ; оставляем запас до refresh. +for i = 0, 99 do + refresh_chord(105.0 + i * 0.50) +end + +key(160.0, "P2.3", 0x80) -- F10 первого запуска. +key(161.5, "P2.1", 0x10) + +type_text(167.0, "SPRCMD .EXE\n") +key(173.0, "P2.3", 0x80) +key(174.5, "P2.1", 0x10) + +type_text(180.0, "SPRCMD .EXE\n") +key(186.0, "P2.3", 0x80) +key(187.5, "P2.1", 0x10) + +table.sort(events, function(a, b) return a[1] < b[1] end) + +local snap_times = { + 14.0, -- root D:; + 20.5, -- SOURCE/TARGET; + 80.0, -- прогресс последнего файла больше 1 МБ; + 100.0, -- 20 файлов показаны в TARGET; + 157.0, -- состояние после 100 refresh; + 164.0, -- первый cleanup, чистый prompt; + 177.0, -- второй cleanup; + 190.0 -- третий cleanup. +} +local snaps_done = {} +local start_time = nil +local ports = nil +local index = 1 +local field_cache = {} + +local function now() + local t = manager.machine.time + return t.seconds + t.attoseconds / 1e18 +end + +local function field(tag, mask) + local cache_key = tag .. "/" .. mask + if field_cache[cache_key] then return field_cache[cache_key] end + for _, candidate in pairs(ports[tag].fields) do + if candidate.mask == mask then + field_cache[cache_key] = candidate + return candidate + end + end + error("Не найдено поле " .. cache_key) +end + +emu.add_machine_reset_notifier(function() + start_time = now() +end) + +emu.register_periodic(function() + if not start_time then return end + if not ports then ports = manager.machine.ioport.ports end + local elapsed = now() - start_time + + while index <= #events and elapsed >= events[index][1] do + local event = events[index] + field(event[2], event[3]):set_value(event[4]) + index = index + 1 + end + + for _, snap_time in ipairs(snap_times) do + if not snaps_done[snap_time] and elapsed >= snap_time then + snaps_done[snap_time] = true + manager.machine.video:snapshot() + print("[p5-stability] snapshot t=" .. elapsed) + end + end + + if elapsed >= 192.0 then + print("[p5-stability] exit t=" .. elapsed) + manager.machine:exit() + end +end) diff --git a/applications/Volkov/tests/mame_p5_viewer.lua b/applications/Volkov/tests/mame_p5_viewer.lua new file mode 100644 index 0000000..e64019c --- /dev/null +++ b/applications/Volkov/tests/mame_p5_viewer.lua @@ -0,0 +1,127 @@ +-- P5: запуск реального mdview2, переключение вида и возврат в Commander. + +local KB = ":kbd:ms_naturl:" +local HOLD = 0.06 +local events = {} + +local function add(t, tag, mask, value) + events[#events + 1] = {t, KB .. tag, mask, value} +end + +local function key(t, tag, mask) + add(t, tag, mask, 1) + add(t + HOLD, tag, mask, 0) +end + +local function chord(t, mod_tag, mod_mask, key_tag, key_mask) + add(t, mod_tag, mod_mask, 1) + add(t + 0.02, key_tag, key_mask, 1) + add(t + 0.08, key_tag, key_mask, 0) + add(t + 0.10, mod_tag, mod_mask, 0) +end + +local chars = { + c={"P1.4",0x08}, d={"P1.4",0x04}, e={"P1.4",0x20}, + m={"P2.0",0x10}, p={"P2.3",0x20}, r={"P1.5",0x01}, + s={"P1.2",0x08}, x={"P1.2",0x10}, [" "]={"P2.4",0x80}, + ["."]={"P2.2",0x10}, ["\n"]={"P2.1",0x10}, + [":"]={"P2.3",0x02,true} +} + +local function type_text(t, value) + for i = 1, #value do + local ch = value:sub(i, i) + local def = chars[ch] + local shifted = false + if not def then + local lower = ch:lower() + def = chars[lower] + shifted = ch ~= lower + else + shifted = def[3] or false + end + if shifted then + chord(t, "P1.7", 0x02, def[1], def[2]) + else + key(t, def[1], def[2]) + end + t = t + 0.14 + end +end + +type_text(8.0, "d:\n") +type_text(9.0, "SPRCMD .EXE\n") + +key(15.0, "P2.1", 0x10) -- Commander -> VIEWER. +key(17.0, "P2.3", 0x10) -- MDVIEW2.EXE после `..`. +key(18.0, "P2.1", 0x10) -- Enter/EXEC; README.MD берётся из child CWD. + +key(28.0, "P1.2", 0x20) -- PgDn внутри mdview2. +key(31.0, "P1.2", 0x80) -- F2: RAW. +key(36.0, "P2.3", 0x80) -- F10: выход из mdview2. + +key(44.0, "P2.3", 0x80) -- F10 Commander. +key(46.0, "P2.1", 0x10) + +table.sort(events, function(a, b) return a[1] < b[1] end) + +local snap_times = { + 16.0, -- Commander в VIEWER; + 25.0, -- реальный MD-экран mdview2; + 29.5, -- mdview2 после PgDn; + 33.5, -- RAW-режим mdview2; + 40.0, -- Commander восстановлен после viewer; + 45.0, -- диалог выхода Commander; + 49.0 -- чистый prompt DSS. +} +local snaps_done = {} +local start_time = nil +local ports = nil +local index = 1 +local field_cache = {} + +local function now() + local t = manager.machine.time + return t.seconds + t.attoseconds / 1e18 +end + +local function field(tag, mask) + local cache_key = tag .. "/" .. mask + if field_cache[cache_key] then return field_cache[cache_key] end + for _, candidate in pairs(ports[tag].fields) do + if candidate.mask == mask then + field_cache[cache_key] = candidate + return candidate + end + end + error("Не найдено поле " .. cache_key) +end + +emu.add_machine_reset_notifier(function() + start_time = now() +end) + +emu.register_periodic(function() + if not start_time then return end + if not ports then ports = manager.machine.ioport.ports end + local elapsed = now() - start_time + + while index <= #events and elapsed >= events[index][1] do + local event = events[index] + field(event[2], event[3]):set_value(event[4]) + index = index + 1 + end + + for _, snap_time in ipairs(snap_times) do + if not snaps_done[snap_time] and elapsed >= snap_time then + snaps_done[snap_time] = true + manager.machine.video:snapshot() + print("[p5-viewer] snapshot t=" .. elapsed) + end + end + + if elapsed >= 51.0 then + print("[p5-viewer] exit t=" .. elapsed) + manager.machine:exit() + end +end) diff --git a/applications/Volkov/tests/mame_p6_sort.lua b/applications/Volkov/tests/mame_p6_sort.lua new file mode 100644 index 0000000..5f20b80 --- /dev/null +++ b/applications/Volkov/tests/mame_p6_sort.lua @@ -0,0 +1,143 @@ +-- 0.2/P6: четыре сортировки, reverse, независимость панелей и refresh. + +local KB = ":kbd:ms_naturl:" +local HOLD = 0.06 +local events = {} + +local function add(t, tag, mask, value) + events[#events + 1] = {t, KB .. tag, mask, value} +end + +local function key(t, tag, mask) + add(t, tag, mask, 1) + add(t + HOLD, tag, mask, 0) +end + +local function chord(t, mod_tag, mod_mask, key_tag, key_mask) + add(t, mod_tag, mod_mask, 1) + add(t + 0.02, key_tag, key_mask, 1) + add(t + 0.08, key_tag, key_mask, 0) + add(t + 0.10, mod_tag, mod_mask, 0) +end + +local chars = { + c={"P1.4",0x08}, d={"P1.4",0x04}, e={"P1.4",0x20}, + m={"P2.0",0x10}, o={"P2.2",0x02}, p={"P2.3",0x20}, + r={"P1.5",0x01}, s={"P1.2",0x08}, t={"P1.5",0x20}, + x={"P1.2",0x10}, [" "]={"P2.4",0x80}, ["."]={"P2.2",0x10}, + ["\n"]={"P2.1",0x10}, [":"]={"P2.3",0x02,true} +} + +local function type_text(t, value) + for i = 1, #value do + local ch = value:sub(i, i) + local def = chars[ch] or chars[ch:lower()] + local shifted = def[3] or ch ~= ch:lower() + if shifted then + chord(t, "P1.7", 0x02, def[1], def[2]) + else + key(t, def[1], def[2]) + end + t = t + 0.14 + end +end + +local function ctrl_f(t, tag, mask) + chord(t, "P1.1", 0x02, tag, mask) +end + +type_text(8.0, "d:\n") +type_text(9.0, "SORTRT.EXE\n") +type_text(12.0, "SPRCMD.EXE\n") + +key(18.0, "P2.1", 0x10) -- left -> SORTL. +key(20.0, "P1.6", 0x20) -- Tab -> right. +key(21.0, "P2.3", 0x10) -- Down -> SORTR. +key(22.0, "P2.1", 0x10) -- right -> SORTR. +key(24.0, "P1.6", 0x20) -- left active again. + +ctrl_f(27.0, "P2.6", 0x80) -- Ctrl+F4: extension ascending. +ctrl_f(31.0, "P2.6", 0x80) -- extension descending. +ctrl_f(35.0, "P2.1", 0x80) -- Ctrl+F6: size ascending. +ctrl_f(39.0, "P2.1", 0x80) -- size descending. +ctrl_f(43.0, "P2.1", 0x40) -- Ctrl+F5: date ascending. +ctrl_f(47.0, "P2.1", 0x40) -- date descending. +ctrl_f(51.0, "P2.6", 0x40) -- Ctrl+F3: name ascending. +ctrl_f(55.0, "P2.6", 0x40) -- name descending. + +ctrl_f(59.0, "P2.1", 0x80) -- left size ascending. +ctrl_f(61.0, "P2.1", 0x80) -- left size descending. +key(63.0, "P1.6", 0x20) -- right active, still N+. +ctrl_f(65.0, "P2.6", 0x80) -- right extension ascending. +chord(68.0, "P1.1", 0x02, "P1.5", 0x01) -- refresh keeps E+. + +key(72.0, "P2.3", 0x80) -- F10. +key(74.0, "P2.1", 0x10) -- Enter: cleanup. + +table.sort(events, function(a, b) return a[1] < b[1] end) + +local snap_times = { + 25.5, -- обе панели N+; + 29.0, -- left E+; + 33.0, -- left E-; + 37.0, -- left S+; + 41.0, -- left S-; + 45.0, -- left D+; + 49.0, -- left D-; + 53.0, -- left N+; + 57.0, -- left N-; + 66.5, -- left S-, right E+; + 70.0, -- refresh сохранил right E+; + 76.0 -- чистый DSS prompt. +} +local snaps_done = {} +local start_time = nil +local ports = nil +local index = 1 +local field_cache = {} + +local function now() + local t = manager.machine.time + return t.seconds + t.attoseconds / 1e18 +end + +local function field(tag, mask) + local cache_key = tag .. "/" .. mask + if field_cache[cache_key] then return field_cache[cache_key] end + for _, candidate in pairs(ports[tag].fields) do + if candidate.mask == mask then + field_cache[cache_key] = candidate + return candidate + end + end + error("Не найдено поле " .. cache_key) +end + +emu.add_machine_reset_notifier(function() + start_time = now() +end) + +emu.register_periodic(function() + if not start_time then return end + if not ports then ports = manager.machine.ioport.ports end + local elapsed = now() - start_time + + while index <= #events and elapsed >= events[index][1] do + local event = events[index] + field(event[2], event[3]):set_value(event[4]) + index = index + 1 + end + + for _, snap_time in ipairs(snap_times) do + if not snaps_done[snap_time] and elapsed >= snap_time then + snaps_done[snap_time] = true + manager.machine.video:snapshot() + print("[p6-sort] snapshot t=" .. elapsed) + end + end + + if elapsed >= 78.0 then + print("[p6-sort] exit t=" .. elapsed) + manager.machine:exit() + end +end) diff --git a/applications/Volkov/tests/mame_p7_select.lua b/applications/Volkov/tests/mame_p7_select.lua new file mode 100644 index 0000000..ffe86af --- /dev/null +++ b/applications/Volkov/tests/mame_p7_select.lua @@ -0,0 +1,148 @@ +-- 0.2/P7: Insert, invert, перенос flags через sort и сброс на refresh. + +local KB = ":kbd:ms_naturl:" +local HOLD = 0.06 +local events = {} + +local function add(t, tag, mask, value) + events[#events + 1] = {t, KB .. tag, mask, value} +end + +local function key(t, tag, mask) + add(t, tag, mask, 1) + add(t + HOLD, tag, mask, 0) +end + +local function chord(t, mod_tag, mod_mask, key_tag, key_mask) + add(t, mod_tag, mod_mask, 1) + add(t + 0.02, key_tag, key_mask, 1) + add(t + 0.08, key_tag, key_mask, 0) + add(t + 0.10, mod_tag, mod_mask, 0) +end + +local chars = { + b={"P1.5",0x10}, c={"P1.4",0x08}, d={"P1.4",0x04}, + e={"P1.4",0x20}, i={"P1.4",0x01}, l={"P2.2",0x08}, + m={"P2.0",0x10}, n={"P2.0",0x08}, p={"P2.3",0x20}, + r={"P1.5",0x01}, s={"P1.2",0x08}, t={"P1.5",0x20}, + x={"P1.2",0x10}, [" "]={"P2.4",0x80}, ["."]={"P2.2",0x10}, + ["\n"]={"P2.1",0x10}, [":"]={"P2.3",0x02,true} +} + +local function type_text(t, value) + for i = 1, #value do + local ch = value:sub(i, i) + local def = chars[ch] or chars[ch:lower()] + local shifted = def[3] or ch ~= ch:lower() + if shifted then + chord(t, "P1.7", 0x02, def[1], def[2]) + else + key(t, def[1], def[2]) + end + t = t + 0.14 + end +end + +type_text(8.0, "d:\n") +type_text(9.0, "SELRT.EXE\n") +type_text(12.0, "SPRCMD.EXE\n") + +key(18.0, "P2.1", 0x10) -- left -> SELECT. +key(21.0, "P2.6", 0x01) -- Insert на `..`: запрещён. +key(23.0, "P2.3", 0x10) -- Down -> ADIR. +key(24.0, "P2.6", 0x01) -- выбрать ADIR, cursor -> ZDIR. +key(26.0, "P2.6", 0x01) -- выбрать ZDIR, cursor -> A.BIN. +key(28.0, "P2.6", 0x01) -- выбрать A.BIN, cursor -> B.BIN. +chord(32.0, "P1.1", 0x02, "P2.6", 0x40) -- Ctrl+F3: N-. +chord(36.0, "P1.7", 0x02, "P1.4", 0x80) -- Shift+8 = `*`. +chord(40.0, "P1.1", 0x02, "P1.5", 0x01) -- Ctrl+R: выбор сброшен. + +chord(43.0, "P1.7", 0x02, "P2.1", 0x01) -- Shift+=: `+`. +key(44.0, "P2.1", 0x20) -- стереть default `*.*`. +key(44.3, "P2.1", 0x20) +key(44.6, "P2.1", 0x20) +chord(45.0, "P1.7", 0x02, "P1.4", 0x80) -- `*`. +key(45.3, "P2.2", 0x10) -- `.`. +type_text(45.6, "bin") +key(46.4, "P2.1", 0x10) -- apply `*.bin`. + +key(50.0, "P2.2", 0x20) -- `-`. +key(51.0, "P2.1", 0x20) -- стереть default `*.*`. +key(51.3, "P2.1", 0x20) +key(51.6, "P2.1", 0x20) +type_text(52.0, "b.bin") +key(53.0, "P2.1", 0x10) -- unselect B.BIN. + +chord(56.0, "P1.7", 0x02, "P2.1", 0x01) -- `+`, затем cancel. +key(57.0, "P1.6", 0x40) -- Esc. +key(60.0, "P2.3", 0x80) -- F10. +key(62.0, "P2.1", 0x10) -- Enter: cleanup. + +table.sort(events, function(a, b) return a[1] < b[1] end) + +local snap_times = { + 20.0, -- исходный список; + 22.0, -- `..` не выбран; + 29.5, -- два каталога и A.BIN выбраны, sel=10/003; + 33.5, -- sort N- сохранил flags и текущий B.BIN; + 37.5, -- invert оставил каталоги и B.BIN/C.BIN, sel=50/004; + 41.5, -- refresh сбросил выбор, N- сохранён; + 43.5, -- mask dialog с default `*.*`; + 46.1, -- отредактировано `*.bin`; + 48.0, -- маска выбрала три BIN, sel=60/003; + 54.5, -- `- b.bin` оставил A/C, sel=40/002; + 58.0, -- cancel следующего `+` сохранил выбор; + 64.0 -- чистый DSS prompt. +} +local snaps_done = {} +local start_time = nil +local ports = nil +local index = 1 +local field_cache = {} + +local function now() + local t = manager.machine.time + return t.seconds + t.attoseconds / 1e18 +end + +local function field(tag, mask) + local cache_key = tag .. "/" .. mask + if field_cache[cache_key] then return field_cache[cache_key] end + for _, candidate in pairs(ports[tag].fields) do + if candidate.mask == mask then + field_cache[cache_key] = candidate + return candidate + end + end + error("Не найдено поле " .. cache_key) +end + + +emu.add_machine_reset_notifier(function() + start_time = now() +end) + +emu.register_periodic(function() + if not start_time then return end + if not ports then ports = manager.machine.ioport.ports end + local elapsed = now() - start_time + + while index <= #events and elapsed >= events[index][1] do + local event = events[index] + field(event[2], event[3]):set_value(event[4]) + index = index + 1 + end + + for _, snap_time in ipairs(snap_times) do + if not snaps_done[snap_time] and elapsed >= snap_time then + snaps_done[snap_time] = true + manager.machine.video:snapshot() + print("[p7-select] snapshot t=" .. elapsed) + end + end + + if elapsed >= 66.0 then + print("[p7-select] exit t=" .. elapsed) + manager.machine:exit() + end +end) diff --git a/applications/Volkov/tests/mame_p9_mkdir.lua b/applications/Volkov/tests/mame_p9_mkdir.lua new file mode 100644 index 0000000..3852e11 --- /dev/null +++ b/applications/Volkov/tests/mame_p9_mkdir.lua @@ -0,0 +1,136 @@ +-- 0.2/P9: F7 success, duplicate, invalid 8.3, cancel и навигация. + +local KB = ":kbd:ms_naturl:" +local HOLD = 0.06 +local events = {} + +local function add(t, tag, mask, value) + events[#events + 1] = {t, KB .. tag, mask, value} +end + +local function key(t, tag, mask) + add(t, tag, mask, 1) + add(t + HOLD, tag, mask, 0) +end + +local function chord(t, mod_tag, mod_mask, key_tag, key_mask) + add(t, mod_tag, mod_mask, 1) + add(t + 0.02, key_tag, key_mask, 1) + add(t + 0.08, key_tag, key_mask, 0) + add(t + 0.10, mod_tag, mod_mask, 0) +end + +local chars = { + c={"P1.4",0x08}, d={"P1.4",0x04}, e={"P1.4",0x20}, + g={"P1.5",0x04}, i={"P1.4",0x01}, k={"P1.4",0x02}, + l={"P2.2",0x08}, m={"P2.0",0x10}, n={"P2.0",0x08}, + o={"P2.2",0x02}, p={"P2.3",0x20}, r={"P1.5",0x01}, + s={"P1.2",0x08}, t={"P1.5",0x20}, w={"P1.2",0x04}, + x={"P1.2",0x10}, ["9"]={"P2.2",0x01}, + [" "]={"P2.4",0x80}, ["."]={"P2.2",0x10}, + ["\n"]={"P2.1",0x10}, [":"]={"P2.3",0x02,true} +} + +local function type_text(t, value) + for i = 1, #value do + local ch = value:sub(i, i) + local def = chars[ch] or chars[ch:lower()] + local shifted = def[3] or ch ~= ch:lower() + if shifted then + chord(t, "P1.7", 0x02, def[1], def[2]) + else + key(t, def[1], def[2]) + end + t = t + 0.14 + end +end + +type_text(8.0, "d:\n") +type_text(9.0, "MKRT.EXE\n") +type_text(12.0, "SPRCMD.EXE\n") + +key(18.0, "P2.1", 0x10) -- left -> MKTEST. +key(20.0, "P2.2", 0x40) -- F7. +type_text(21.0, "newdir") +key(22.2, "P2.1", 0x10) -- create. + +key(26.0, "P2.2", 0x40) -- F7 duplicate. +type_text(27.0, "newdir") +key(28.2, "P2.1", 0x10) + +key(32.0, "P2.2", 0x40) -- F7 invalid basename > 8. +type_text(33.0, "toolong99") +key(34.5, "P2.1", 0x10) + +key(38.0, "P2.2", 0x40) -- F7 then cancel. +key(39.0, "P1.6", 0x40) +key(42.0, "P2.3", 0x10) -- Down -> NEWDIR. +key(43.0, "P2.1", 0x10) -- enter NEWDIR. +key(47.0, "P2.1", 0x20) -- Backspace -> MKTEST. +key(51.0, "P2.3", 0x80) -- F10. +key(53.0, "P2.1", 0x10) -- cleanup. + +table.sort(events, function(a, b) return a[1] < b[1] end) + +local snap_times = { + 19.0, -- исходный MKTEST; + 20.5, -- пустой F7 dialog; + 23.5, -- NEWDIR создан; + 29.5, -- duplicate managed error; + 35.5, -- invalid 8.3 rejected; + 40.0, -- cancel не изменил каталог; + 44.5, -- вошли в NEWDIR; + 48.5, -- вернулись в MKTEST; + 55.0 -- чистый DSS prompt. +} +local snaps_done = {} +local start_time = nil +local ports = nil +local index = 1 +local field_cache = {} + +local function now() + local t = manager.machine.time + return t.seconds + t.attoseconds / 1e18 +end + +local function field(tag, mask) + local cache_key = tag .. "/" .. mask + if field_cache[cache_key] then return field_cache[cache_key] end + for _, candidate in pairs(ports[tag].fields) do + if candidate.mask == mask then + field_cache[cache_key] = candidate + return candidate + end + end + error("Не найдено поле " .. cache_key) +end + +emu.add_machine_reset_notifier(function() + start_time = now() +end) + +emu.register_periodic(function() + if not start_time then return end + if not ports then ports = manager.machine.ioport.ports end + local elapsed = now() - start_time + + while index <= #events and elapsed >= events[index][1] do + local event = events[index] + field(event[2], event[3]):set_value(event[4]) + index = index + 1 + end + + for _, snap_time in ipairs(snap_times) do + if not snaps_done[snap_time] and elapsed >= snap_time then + snaps_done[snap_time] = true + manager.machine.video:snapshot() + print("[p9-mkdir] snapshot t=" .. elapsed) + end + end + + if elapsed >= 57.0 then + print("[p9-mkdir] exit t=" .. elapsed) + manager.machine:exit() + end +end) diff --git a/applications/Volkov/tests/p10_rename_fixture.py b/applications/Volkov/tests/p10_rename_fixture.py new file mode 100755 index 0000000..c25516b --- /dev/null +++ b/applications/Volkov/tests/p10_rename_fixture.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +"""Создаёт и проверяет HDD-fixture команды F6 local rename.""" + +from __future__ import annotations + +import pathlib +import sys + + +def create(root: pathlib.Path) -> None: + panel = root / "RENTEST" + panel.mkdir(parents=True) + (panel / "OLD.TXT").write_bytes(b"old payload\r\n") + (panel / "EXIST.TXT").write_bytes(b"existing payload\r\n") + (panel / "UIOLD.TXT").write_bytes(b"ui payload\r\n") + old_dir = panel / "OLDDIR" + old_dir.mkdir() + (old_dir / "KEEP.BIN").write_bytes(b"dir payload\r\n") + + +def verify(root: pathlib.Path) -> None: + expected = [ + "PASS CWD captured before rename matrix", + "PASS file renamed without data change", + "PASS existing target blocks rename and stays intact", + "PASS directory renamed with contents intact", + "PASS failed directory change preserves CWD", + "PASS successful and rejected renames restore CWD", + "ALL PASS", + ] + failures: list[str] = [] + result = root / "RENRES.TXT" + panel = root / "RENTEST" + + if not result.is_file(): + failures.append("нет RENRES.TXT") + else: + text = result.read_bytes().decode("cp866", errors="replace") + for line in expected: + if line not in text: + failures.append(f"нет строки результата: {line}") + + files = { + "NEW.TXT": b"old payload\r\n", + "EXIST.TXT": b"existing payload\r\n", + "FINAL.BIN": b"ui payload\r\n", + "NEWDIR/KEEP.BIN": b"dir payload\r\n", + } + for relative, content in files.items(): + path = panel / relative + if not path.is_file() or path.read_bytes() != content: + failures.append(f"неверное содержимое {relative}") + for unwanted in ("OLD.TXT", "UIOLD.TXT", "OLDDIR"): + if (panel / unwanted).exists(): + failures.append(f"старое имя осталось: {unwanted}") + + if failures: + for failure in failures: + print(f"FAIL: {failure}") + raise SystemExit(1) + print("PASS: file/dir rename, no-overwrite, CWD restore и UI F6.") + + +def main() -> None: + if len(sys.argv) != 3 or sys.argv[1] not in {"create", "verify"}: + raise SystemExit(f"usage: {sys.argv[0]} create|verify DIR") + root = pathlib.Path(sys.argv[2]) + if sys.argv[1] == "create": + create(root) + else: + verify(root) + + +if __name__ == "__main__": + main() diff --git a/applications/Volkov/tests/p10_rename_probe.c b/applications/Volkov/tests/p10_rename_probe.c new file mode 100644 index 0000000..9c25636 --- /dev/null +++ b/applications/Volkov/tests/p10_rename_probe.c @@ -0,0 +1,110 @@ +/* + * p10_rename_probe.c — target-регрессия локального F6/RENAME. + * + * Проверяет файл, каталог, запрет overwrite и восстановление CWD через тот + * же низкоуровневый helper, который вызывает Commander. + */ + +#include +#include +#include +#include +#include +#include "sc_file_ops.h" + +static int result_fd = -1; +static uint8_t failures; +static char cwd_before[256]; +static char cwd_after[256]; +static char cwd_scratch[256]; + +static void log_text(const char *text) +{ + const char *end = text; + while (*end != '\0') end++; + if (result_fd >= 0) { + (void)write(result_fd, text, (uint16_t)(end - text)); + } +} + +static void check(uint8_t condition, const char *name) +{ + log_text(condition ? "PASS " : "FAIL "); + if (!condition) failures++; + log_text(name); + log_text("\r\n"); +} + +static uint8_t file_has(const char *path, const char *expected) +{ + char data[24]; + int fd = open(path, O_RDONLY); + int got; + uint16_t length = 0u; + + if (fd < 0) return 0u; + while (expected[length] != '\0') length++; + got = read(fd, data, sizeof(data)); + (void)close(fd); + return got == (int)length && memcmp(data, expected, length) == 0; +} + +static uint8_t file_absent(const char *path) +{ + int fd; + errno = 0; + fd = open(path, O_RDONLY); + if (fd >= 0) { + (void)close(fd); + return 0u; + } + return errno == ENOENT || errno == ENOPATH; +} + +int main(void) +{ + int rc; + int collision_errno; + int entered; + + result_fd = open("D:\\RENRES.TXT", O_WRONLY | O_CREAT | O_TRUNC); + check(getcwd(cwd_before, sizeof(cwd_before)) != 0, + "CWD captured before rename matrix"); + + rc = sc_rename_local("D:\\RENTEST", "OLD.TXT", "NEW.TXT", + cwd_scratch, sizeof(cwd_scratch)); + check(rc == 0 && file_absent("D:\\RENTEST\\OLD.TXT") && + file_has("D:\\RENTEST\\NEW.TXT", "old payload\r\n"), + "file renamed without data change"); + + errno = 0; + rc = sc_rename_local("D:\\RENTEST", "NEW.TXT", "EXIST.TXT", + cwd_scratch, sizeof(cwd_scratch)); + collision_errno = errno; + check(rc != 0 && collision_errno == EEXIST && + file_has("D:\\RENTEST\\NEW.TXT", "old payload\r\n") && + file_has("D:\\RENTEST\\EXIST.TXT", "existing payload\r\n"), + "existing target blocks rename and stays intact"); + + rc = sc_rename_local("D:\\RENTEST", "OLDDIR", "NEWDIR", + cwd_scratch, sizeof(cwd_scratch)); + entered = chdir("D:\\RENTEST\\NEWDIR"); + if (entered == 0) entered = chdir(cwd_before); + check(rc == 0 && entered == 0 && + file_has("D:\\RENTEST\\NEWDIR\\KEEP.BIN", "dir payload\r\n"), + "directory renamed with contents intact"); + + errno = 0; + rc = sc_rename_local("D:\\MISSING", "A.TXT", "B.TXT", + cwd_scratch, sizeof(cwd_scratch)); + check(rc != 0 && getcwd(cwd_after, sizeof(cwd_after)) != 0 && + strcmp(cwd_before, cwd_after) == 0, + "failed directory change preserves CWD"); + check(getcwd(cwd_after, sizeof(cwd_after)) != 0 && + strcmp(cwd_before, cwd_after) == 0, + "successful and rejected renames restore CWD"); + + if (failures == 0u) log_text("ALL PASS\r\n"); + if (result_fd >= 0) (void)close(result_fd); + return failures; +} diff --git a/applications/Volkov/tests/p11_delete_fixture.py b/applications/Volkov/tests/p11_delete_fixture.py new file mode 100755 index 0000000..c09b7d1 --- /dev/null +++ b/applications/Volkov/tests/p11_delete_fixture.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +"""Создаёт и проверяет HDD-fixture команды F8 delete.""" + +from __future__ import annotations + +import pathlib +import sys + + +def create(root: pathlib.Path) -> None: + panel = root / "DELTEST" + panel.mkdir(parents=True) + (panel / "TARGET.TXT").write_bytes(b"target payload\r\n") + (panel / "REMOVE.TXT").write_bytes(b"remove payload\r\n") + (panel / "KEEP.TXT").write_bytes(b"keep remains\r\n") + (panel / "TEMPDIR").mkdir() + (panel / "EMPTYDIR").mkdir() + nonempty = panel / "NONEMPTY" + nonempty.mkdir() + (nonempty / "CHILD.TXT").write_bytes(b"child remains\r\n") + + +def verify(root: pathlib.Path) -> None: + expected = [ + "PASS CWD captured before delete matrix", + "PASS unlink removes one regular file", + "PASS rmdir removes one empty directory", + "PASS nonempty directory is not removed", + "PASS unrelated file remains intact", + "PASS full-path delete operations preserve CWD", + "ALL PASS", + ] + failures: list[str] = [] + result = root / "DELRES.TXT" + panel = root / "DELTEST" + + if not result.is_file(): + failures.append("нет DELRES.TXT") + else: + text = result.read_bytes().decode("cp866", errors="replace") + for line in expected: + if line not in text: + failures.append(f"нет строки результата: {line}") + + absent = ("TARGET.TXT", "TEMPDIR", "REMOVE.TXT", "EMPTYDIR") + for name in absent: + if (panel / name).exists(): + failures.append(f"объект не удалён: {name}") + remaining = { + "KEEP.TXT": b"keep remains\r\n", + "NONEMPTY/CHILD.TXT": b"child remains\r\n", + } + for name, content in remaining.items(): + path = panel / name + if not path.is_file() or path.read_bytes() != content: + failures.append(f"объект изменён: {name}") + + if failures: + for failure in failures: + print(f"FAIL: {failure}") + raise SystemExit(1) + print("PASS: file/empty-dir delete, cancel, guards, nonempty и CWD.") + + +def main() -> None: + if len(sys.argv) != 3 or sys.argv[1] not in {"create", "verify"}: + raise SystemExit(f"usage: {sys.argv[0]} create|verify DIR") + root = pathlib.Path(sys.argv[2]) + if sys.argv[1] == "create": + create(root) + else: + verify(root) + + +if __name__ == "__main__": + main() diff --git a/applications/Volkov/tests/p11_delete_probe.c b/applications/Volkov/tests/p11_delete_probe.c new file mode 100644 index 0000000..ce7d60e --- /dev/null +++ b/applications/Volkov/tests/p11_delete_probe.c @@ -0,0 +1,85 @@ +/* p11_delete_probe.c — target-регрессия однообъектного F8 delete. */ + +#include +#include +#include +#include +#include + +static int result_fd = -1; +static uint8_t failures; +static char cwd_before[256]; +static char cwd_after[256]; + +static void log_text(const char *text) +{ + const char *end = text; + while (*end != '\0') end++; + if (result_fd >= 0) { + (void)write(result_fd, text, (uint16_t)(end - text)); + } +} + +static void check(uint8_t condition, const char *name) +{ + log_text(condition ? "PASS " : "FAIL "); + if (!condition) failures++; + log_text(name); + log_text("\r\n"); +} + +static uint8_t file_has(const char *path, const char *expected) +{ + char data[24]; + int fd = open(path, O_RDONLY); + int got; + uint16_t length = 0u; + + if (fd < 0) return 0u; + while (expected[length] != '\0') length++; + got = read(fd, data, sizeof(data)); + (void)close(fd); + return got == (int)length && memcmp(data, expected, length) == 0; +} + +static uint8_t file_absent(const char *path) +{ + int fd; + errno = 0; + fd = open(path, O_RDONLY); + if (fd >= 0) { + (void)close(fd); + return 0u; + } + return errno == ENOENT || errno == ENOPATH; +} + +int main(void) +{ + int rc; + + result_fd = open("D:\\DELRES.TXT", O_WRONLY | O_CREAT | O_TRUNC); + check(getcwd(cwd_before, sizeof(cwd_before)) != 0, + "CWD captured before delete matrix"); + check(unlink("D:\\DELTEST\\TARGET.TXT") == 0 && + file_absent("D:\\DELTEST\\TARGET.TXT"), + "unlink removes one regular file"); + check(rmdir("D:\\DELTEST\\TEMPDIR") == 0, + "rmdir removes one empty directory"); + + errno = 0; + rc = rmdir("D:\\DELTEST\\NONEMPTY"); + check(rc != 0 && + file_has("D:\\DELTEST\\NONEMPTY\\CHILD.TXT", + "child remains\r\n"), + "nonempty directory is not removed"); + check(file_has("D:\\DELTEST\\KEEP.TXT", "keep remains\r\n"), + "unrelated file remains intact"); + check(getcwd(cwd_after, sizeof(cwd_after)) != 0 && + strcmp(cwd_before, cwd_after) == 0, + "full-path delete operations preserve CWD"); + + if (failures == 0u) log_text("ALL PASS\r\n"); + if (result_fd >= 0) (void)close(result_fd); + return failures; +} diff --git a/applications/Volkov/tests/p12_group_copy_fixture.py b/applications/Volkov/tests/p12_group_copy_fixture.py new file mode 100755 index 0000000..f7d59d4 --- /dev/null +++ b/applications/Volkov/tests/p12_group_copy_fixture.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +"""Создаёт и проверяет HDD-fixture группового F5.""" + +from __future__ import annotations + +import pathlib +import sys + + +def payload(size: int, seed: int) -> bytes: + return bytes(((index * 37 + seed) & 0xFF) for index in range(size)) + + +def create(root: pathlib.Path) -> None: + source = root / "GRSRC" + target = root / "GRDST" + source.mkdir(parents=True) + target.mkdir() + (source / "A.BIN").write_bytes(payload(10, 3)) + (source / "B.BIN").write_bytes(payload(4097, 11)) + (source / "C.BIN").write_bytes(b"") + directory = source / "ADIR" + directory.mkdir() + (directory / "INSIDE.TXT").write_bytes(b"must not be copied\r\n") + + +def verify(root: pathlib.Path) -> None: + source = root / "GRSRC" + target = root / "GRDST" + failures: list[str] = [] + expected = { + "A.BIN": payload(10, 3), + "B.BIN": payload(4097, 11), + "C.BIN": b"", + } + + for name, content in expected.items(): + source_path = source / name + target_path = target / name + if not source_path.is_file() or source_path.read_bytes() != content: + failures.append(f"source изменён: {name}") + if not target_path.is_file() or target_path.read_bytes() != content: + failures.append(f"target не совпадает: {name}") + target_inside = target / "ADIR" / "INSIDE.TXT" + if (not target_inside.is_file() or + target_inside.read_bytes() != b"must not be copied\r\n"): + failures.append("выбранный каталог не скопирован рекурсивно") + temporary = [path.name for path in target.rglob("*") + if path.name.upper().endswith(".TMP")] + if temporary: + failures.append(f"остались temp: {temporary}") + inside = source / "ADIR" / "INSIDE.TXT" + if not inside.is_file() or inside.read_bytes() != b"must not be copied\r\n": + failures.append("исходный выбранный каталог изменён") + + if failures: + for failure in failures: + print(f"FAIL: {failure}") + raise SystemExit(1) + print("PASS: 3 selected files and selected directory copied; no temp.") + + +def main() -> None: + if len(sys.argv) != 3 or sys.argv[1] not in {"create", "verify"}: + raise SystemExit(f"usage: {sys.argv[0]} create|verify DIR") + root = pathlib.Path(sys.argv[2]) + if sys.argv[1] == "create": + create(root) + else: + verify(root) + + +if __name__ == "__main__": + main() diff --git a/applications/Volkov/tests/p13_group_delete_fixture.py b/applications/Volkov/tests/p13_group_delete_fixture.py new file mode 100755 index 0000000..0a2998c --- /dev/null +++ b/applications/Volkov/tests/p13_group_delete_fixture.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +"""Создаёт и проверяет HDD-fixture группового F8.""" + +from __future__ import annotations + +import pathlib +import sys + + +def create(root: pathlib.Path) -> None: + panel = root / "GDEL" + panel.mkdir(parents=True) + (panel / "A.TXT").write_bytes(b"delete A\r\n") + (panel / "B.BIN").write_bytes(b"delete B payload\r\n") + (panel / "KEEP.TXT").write_bytes(b"keep payload\r\n") + directory = panel / "ADIR" + directory.mkdir() + (directory / "INSIDE.TXT").write_bytes(b"directory payload\r\n") + (panel / "EMPTYDIR").mkdir() + + +def verify(root: pathlib.Path) -> None: + panel = root / "GDEL" + failures: list[str] = [] + + for name in ("A.TXT", "B.BIN"): + if (panel / name).exists(): + failures.append(f"выбранный файл не удалён: {name}") + expected = { + "KEEP.TXT": b"keep payload\r\n", + "ADIR/INSIDE.TXT": b"directory payload\r\n", + } + for name, content in expected.items(): + path = panel / name + if not path.is_file() or path.read_bytes() != content: + failures.append(f"сохранённый объект изменён: {name}") + if not (panel / "EMPTYDIR").is_dir(): + failures.append("выбранный пустой каталог ошибочно удалён") + + if failures: + for failure in failures: + print(f"FAIL: {failure}") + raise SystemExit(1) + print("PASS: selected files deleted; selected directories and KEEP preserved.") + + +def main() -> None: + if len(sys.argv) != 3 or sys.argv[1] not in {"create", "verify"}: + raise SystemExit(f"usage: {sys.argv[0]} create|verify DIR") + root = pathlib.Path(sys.argv[2]) + if sys.argv[1] == "create": + create(root) + else: + verify(root) + + +if __name__ == "__main__": + main() diff --git a/applications/Volkov/tests/p14_default_colors_fixture.py b/applications/Volkov/tests/p14_default_colors_fixture.py new file mode 100755 index 0000000..ac31e55 --- /dev/null +++ b/applications/Volkov/tests/p14_default_colors_fixture.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +"""Создаёт и проверяет HDD-fixture стандартных цветов имён.""" + +from __future__ import annotations + +import pathlib +import sys + + +CONTENTS = { + "APP.EXE": b"EXE colour sample\r\n", + "ARCHIVE.ZIP": b"ZIP colour sample\r\n", + "NOEXT": b"no extension sample\r\n", + "README.TXT": b"text colour sample\r\n", + "SUBDIR/INSIDE.DAT": b"directory sample\r\n", +} + + +def create(root: pathlib.Path) -> None: + panel = root / "COLORS" + for name, content in CONTENTS.items(): + path = panel / name + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(content) + + +def verify(root: pathlib.Path) -> None: + panel = root / "COLORS" + failures: list[str] = [] + for name, content in CONTENTS.items(): + path = panel / name + if not path.is_file() or path.read_bytes() != content: + failures.append(f"объект отсутствует или изменён: {name}") + if failures: + for failure in failures: + print(f"FAIL: {failure}") + raise SystemExit(1) + print("PASS: colour fixture preserved.") + + +def main() -> None: + if len(sys.argv) != 3 or sys.argv[1] not in {"create", "verify"}: + raise SystemExit(f"usage: {sys.argv[0]} create|verify DIR") + root = pathlib.Path(sys.argv[2]) + if sys.argv[1] == "create": + create(root) + else: + verify(root) + + +if __name__ == "__main__": + main() diff --git a/applications/Volkov/tests/p15_tree_cancel_fixture.py b/applications/Volkov/tests/p15_tree_cancel_fixture.py new file mode 100755 index 0000000..9064042 --- /dev/null +++ b/applications/Volkov/tests/p15_tree_cancel_fixture.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +"""Создаёт и проверяет fixture отмены рекурсивного F5.""" + +from __future__ import annotations + +import pathlib +import sys + + +def payload(size: int) -> bytes: + return bytes(((index * 29 + 17) & 0xFF) for index in range(size)) + + +BIG = payload(4 * 1024 * 1024) +AFTER = b"may be committed before traversal reaches BIG\r\n" + + +def create(root: pathlib.Path) -> None: + tree = root / "CSRC" / "TREE" + tree.mkdir(parents=True) + (root / "CDST").mkdir() + (tree / "BIG.BIN").write_bytes(BIG) + (tree / "AFTER.TXT").write_bytes(AFTER) + + +def verify(root: pathlib.Path) -> None: + source = root / "CSRC" / "TREE" + target = root / "CDST" / "TREE" + failures: list[str] = [] + + if (source / "BIG.BIN").read_bytes() != BIG: + failures.append("source BIG.BIN изменён") + if (source / "AFTER.TXT").read_bytes() != AFTER: + failures.append("source AFTER.TXT изменён") + if not target.is_dir(): + failures.append("root target-каталог не был создан до cancel") + else: + big_target = target / "BIG.BIN" + if big_target.exists(): + failures.append("BIG.BIN committed несмотря на ранний cancel") + after_target = target / "AFTER.TXT" + if after_target.exists() and after_target.read_bytes() != AFTER: + failures.append("частично завершённый AFTER.TXT повреждён") + temporary = [path.name for path in target.iterdir() + if path.name.upper().endswith(".TMP")] + if temporary: + failures.append(f"после cancel остались temp: {temporary}") + + if failures: + for failure in failures: + print(f"FAIL: {failure}") + raise SystemExit(1) + print("PASS: tree cancel removed active temp; source intact; no BIG commit.") + + +def main() -> None: + if len(sys.argv) != 3 or sys.argv[1] not in {"create", "verify"}: + raise SystemExit(f"usage: {sys.argv[0]} create|verify DIR") + root = pathlib.Path(sys.argv[2]) + if sys.argv[1] == "create": + create(root) + else: + verify(root) + + +if __name__ == "__main__": + main() diff --git a/applications/Volkov/tests/p15_tree_copy_fixture.py b/applications/Volkov/tests/p15_tree_copy_fixture.py new file mode 100755 index 0000000..fd6f0f2 --- /dev/null +++ b/applications/Volkov/tests/p15_tree_copy_fixture.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +"""Создаёт и проверяет HDD-fixture рекурсивного F5 одного каталога.""" + +from __future__ import annotations + +import pathlib +import sys + + +def payload(size: int, seed: int) -> bytes: + return bytes(((index * 53 + seed) & 0xFF) for index in range(size)) + + +def create(root: pathlib.Path) -> None: + tree = root / "SRC" / "TREE" + (root / "DST").mkdir(parents=True) + (tree / "EMPTY").mkdir(parents=True) + (tree / "ROOT.TXT").write_bytes(b"recursive root\r\n") + sub = tree / "SUB" + sub.mkdir() + (sub / "A.BIN").write_bytes(payload(4097, 7)) + (sub / "ZERO.DAT").write_bytes(b"") + + deep = sub + for level in range(1, 17): + deep = deep / f"L{level:02d}" + deep.mkdir() + (deep / "BOTTOM.TXT").write_bytes(b"depth sixteen\r\n") + + +def tree_snapshot(root: pathlib.Path) -> dict[str, bytes | None]: + result: dict[str, bytes | None] = {} + for path in sorted(root.rglob("*")): + relative = path.relative_to(root).as_posix().upper() + result[relative] = None if path.is_dir() else path.read_bytes() + return result + + +def verify(root: pathlib.Path) -> None: + source = root / "SRC" / "TREE" + target = root / "DST" / "TREE" + failures: list[str] = [] + + if not source.is_dir() or not target.is_dir(): + failures.append("source или target TREE отсутствует") + elif tree_snapshot(source) != tree_snapshot(target): + failures.append("рекурсивное дерево target не совпадает с source") + + temporary = [path.as_posix() for path in (root / "DST").rglob("*") + if path.name.upper().endswith(".TMP")] + if temporary: + failures.append(f"остались temp: {temporary}") + + if failures: + for failure in failures: + print(f"FAIL: {failure}") + raise SystemExit(1) + print("PASS: recursive tree copied exactly; depth=16; no temp.") + + +def main() -> None: + if len(sys.argv) != 3 or sys.argv[1] not in {"create", "verify"}: + raise SystemExit(f"usage: {sys.argv[0]} create|verify DIR") + root = pathlib.Path(sys.argv[2]) + if sys.argv[1] == "create": + create(root) + else: + verify(root) + + +if __name__ == "__main__": + main() diff --git a/applications/Volkov/tests/p15_tree_self_fixture.py b/applications/Volkov/tests/p15_tree_self_fixture.py new file mode 100755 index 0000000..0a5578d --- /dev/null +++ b/applications/Volkov/tests/p15_tree_self_fixture.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +"""Создаёт и проверяет fixture запрета target внутри source.""" + +from __future__ import annotations + +import pathlib +import sys + + +CONTENT = b"self-target guard\r\n" + + +def create(root: pathlib.Path) -> None: + tree = root / "SRC" / "TREE" + (tree / "INNER").mkdir(parents=True) + (tree / "FILE.TXT").write_bytes(CONTENT) + + +def verify(root: pathlib.Path) -> None: + tree = root / "SRC" / "TREE" + failures: list[str] = [] + if (tree / "FILE.TXT").read_bytes() != CONTENT: + failures.append("source FILE.TXT изменён") + inner = tree / "INNER" + if not inner.is_dir() or any(inner.iterdir()): + failures.append("внутри source появился рекурсивный target") + temporary = [path.as_posix() for path in tree.rglob("*") + if path.name.upper().endswith(".TMP")] + if temporary: + failures.append(f"остались temp: {temporary}") + if failures: + for failure in failures: + print(f"FAIL: {failure}") + raise SystemExit(1) + print("PASS: target-inside-source refused before mutation.") + + +def main() -> None: + if len(sys.argv) != 3 or sys.argv[1] not in {"create", "verify"}: + raise SystemExit(f"usage: {sys.argv[0]} create|verify DIR") + root = pathlib.Path(sys.argv[2]) + if sys.argv[1] == "create": + create(root) + else: + verify(root) + + +if __name__ == "__main__": + main() diff --git a/applications/Volkov/tests/p16_group_tree_copy_fixture.py b/applications/Volkov/tests/p16_group_tree_copy_fixture.py new file mode 100755 index 0000000..d0ddf56 --- /dev/null +++ b/applications/Volkov/tests/p16_group_tree_copy_fixture.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +"""Создаёт и проверяет group F5 с несколькими directory roots.""" + +from __future__ import annotations + +import pathlib +import sys + + +def payload(size: int, seed: int) -> bytes: + return bytes(((index * 41 + seed) & 0xFF) for index in range(size)) + + +def create(root: pathlib.Path) -> None: + source = root / "GSRC" + target = root / "GDST" + target.mkdir(parents=True) + adir = source / "ADIR" + bdir = source / "BDIR" + (adir / "EMPTY").mkdir(parents=True) + bdir.mkdir(parents=True) + (adir / "A1.BIN").write_bytes(payload(10, 3)) + (bdir / "B1.TXT").write_bytes(b"second root\r\n") + (source / "ROOT.BIN").write_bytes(payload(4097, 9)) + (source / "KEEP.TXT").write_bytes(b"not selected\r\n") + + +def snapshot(root: pathlib.Path) -> dict[str, bytes | None]: + result: dict[str, bytes | None] = {} + for path in sorted(root.rglob("*")): + name = path.relative_to(root).as_posix().upper() + result[name] = None if path.is_dir() else path.read_bytes() + return result + + +def verify(root: pathlib.Path) -> None: + source = root / "GSRC" + target = root / "GDST" + failures: list[str] = [] + expected = { + "ADIR": None, + "ADIR/A1.BIN": payload(10, 3), + "ADIR/EMPTY": None, + "BDIR": None, + "BDIR/B1.TXT": b"second root\r\n", + "ROOT.BIN": payload(4097, 9), + } + if snapshot(target) != expected: + failures.append("target не равен двум выбранным деревьям и ROOT.BIN") + if not (source / "KEEP.TXT").is_file(): + failures.append("source KEEP.TXT отсутствует") + if (target / "KEEP.TXT").exists(): + failures.append("невыбранный KEEP.TXT ошибочно скопирован") + temporary = [path.as_posix() for path in target.rglob("*") + if path.name.upper().endswith(".TMP")] + if temporary: + failures.append(f"остались temp: {temporary}") + if failures: + for failure in failures: + print(f"FAIL: {failure}") + raise SystemExit(1) + print("PASS: selected file and two directory roots copied exactly.") + + +def main() -> None: + if len(sys.argv) != 3 or sys.argv[1] not in {"create", "verify"}: + raise SystemExit(f"usage: {sys.argv[0]} create|verify DIR") + root = pathlib.Path(sys.argv[2]) + if sys.argv[1] == "create": + create(root) + else: + verify(root) + + +if __name__ == "__main__": + main() diff --git a/applications/Volkov/tests/p17_tree_delete_cancel_fixture.py b/applications/Volkov/tests/p17_tree_delete_cancel_fixture.py new file mode 100644 index 0000000..6be2c6f --- /dev/null +++ b/applications/Volkov/tests/p17_tree_delete_cancel_fixture.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +"""Создаёт и проверяет runtime-cancel рекурсивного группового F8.""" + +from __future__ import annotations + +import pathlib +import sys + + +DIRECTORY_COUNT = 180 + + +def payload(index: int) -> bytes: + return bytes(((offset * 17 + index) & 0xFF) for offset in range(97)) + + +def create(root: pathlib.Path) -> None: + panel = root / "CDEL" + tree = panel / "TREE" + tree.mkdir(parents=True) + + for index in range(DIRECTORY_COUNT): + child = tree / f"D{index:03d}" + child.mkdir() + (child / "ITEM.BIN").write_bytes(payload(index)) + + (panel / "TOP.BIN").write_bytes(b"top-level selected file\r\n") + (panel / "KEEP.TXT").write_bytes(b"unselected cancel sentinel\r\n") + + +def verify(root: pathlib.Path) -> None: + panel = root / "CDEL" + failures: list[str] = [] + + if (panel / "TOP.BIN").exists(): + failures.append("завершённое удаление TOP.BIN ошибочно откатилось") + + keep = panel / "KEEP.TXT" + if not keep.is_file() or keep.read_bytes() != b"unselected cancel sentinel\r\n": + failures.append("невыбранный KEEP.TXT отсутствует или изменён") + + tree = panel / "TREE" + if not tree.is_dir(): + failures.append("TREE успел удалиться целиком: runtime-cancel не подтверждён") + else: + remaining = list(tree.glob("D*/ITEM.BIN")) + if not remaining: + failures.append("после cancel не осталось ни одного файла дерева") + for path in remaining: + index = int(path.parent.name[1:]) + if path.read_bytes() != payload(index): + failures.append(f"повреждён оставшийся файл: {path.name}") + break + + if failures: + for failure in failures: + print(f"FAIL: {failure}") + raise SystemExit(1) + print("PASS: runtime cancel kept remaining tree; completed TOP delete remains.") + + +def main() -> None: + if len(sys.argv) != 3 or sys.argv[1] not in {"create", "verify"}: + raise SystemExit(f"usage: {sys.argv[0]} create|verify DIR") + root = pathlib.Path(sys.argv[2]) + if sys.argv[1] == "create": + create(root) + else: + verify(root) + + +if __name__ == "__main__": + main() diff --git a/applications/Volkov/tests/p17_tree_delete_fixture.py b/applications/Volkov/tests/p17_tree_delete_fixture.py new file mode 100644 index 0000000..df90169 --- /dev/null +++ b/applications/Volkov/tests/p17_tree_delete_fixture.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +"""Создаёт и проверяет HDD-fixture рекурсивного группового F8.""" + +from __future__ import annotations + +import pathlib +import sys + + +def payload(size: int, seed: int) -> bytes: + return bytes(((index * 29 + seed) & 0xFF) for index in range(size)) + + +def create(root: pathlib.Path) -> None: + panel = root / "RDEL" + adir = panel / "ADIR" + bdir = panel / "BDIR" + keepdir = panel / "KEEPDIR" + + (adir / "EMPTY").mkdir(parents=True) + bdir.mkdir(parents=True) + keepdir.mkdir(parents=True) + + # Несколько файлов в одном каталоге проверяют, что F_NEXT продолжает + # перечисление после удаления только что возвращённой записи. + for index, size in enumerate((0, 1, 16, 4097)): + (adir / f"A{index}.BIN").write_bytes(payload(size, index + 3)) + + deep = adir + for depth in range(1, 13): + deep = deep / f"D{depth:02d}" + deep.mkdir() + (deep / f"F{depth:02d}.TXT").write_bytes( + payload(depth * 7, depth + 19) + ) + + (bdir / "B0.TXT").write_bytes(b"delete second root\r\n") + (bdir / "B1.BIN").write_bytes(payload(513, 71)) + (panel / "TOP.BIN").write_bytes(payload(33, 91)) + + (panel / "KEEP.TXT").write_bytes(b"keep top-level payload\r\n") + (keepdir / "SAFE.BIN").write_bytes(payload(257, 111)) + + +def verify(root: pathlib.Path) -> None: + panel = root / "RDEL" + failures: list[str] = [] + + for name in ("ADIR", "BDIR", "TOP.BIN"): + if (panel / name).exists(): + failures.append(f"выбранный объект не удалён: {name}") + + keep_file = panel / "KEEP.TXT" + if not keep_file.is_file() or keep_file.read_bytes() != b"keep top-level payload\r\n": + failures.append("невыбранный KEEP.TXT отсутствует или изменён") + + safe = panel / "KEEPDIR" / "SAFE.BIN" + if not safe.is_file() or safe.read_bytes() != payload(257, 111): + failures.append("невыбранное дерево KEEPDIR изменено") + + remaining = sorted( + path.relative_to(panel).as_posix().upper() + for path in panel.rglob("*") + ) + if remaining != ["KEEP.TXT", "KEEPDIR", "KEEPDIR/SAFE.BIN"]: + failures.append(f"неожиданный остаток RDEL: {remaining}") + + if failures: + for failure in failures: + print(f"FAIL: {failure}") + raise SystemExit(1) + print("PASS: selected file and two directory trees deleted; KEEP preserved.") + + +def main() -> None: + if len(sys.argv) != 3 or sys.argv[1] not in {"create", "verify"}: + raise SystemExit(f"usage: {sys.argv[0]} create|verify DIR") + root = pathlib.Path(sys.argv[2]) + if sys.argv[1] == "create": + create(root) + else: + verify(root) + + +if __name__ == "__main__": + main() diff --git a/applications/Volkov/tests/p18_metadata_fixture.py b/applications/Volkov/tests/p18_metadata_fixture.py new file mode 100644 index 0000000..5bdb120 --- /dev/null +++ b/applications/Volkov/tests/p18_metadata_fixture.py @@ -0,0 +1,175 @@ +#!/usr/bin/env python3 +"""Создаёт payload и проверяет точные FAT16-метаданные после F5.""" + +from __future__ import annotations + +import datetime +import os +import pathlib +import struct +import sys + + +EXPECTED_ATTR = 0x27 # readonly + hidden + system + archive +EXPECTED_DATE = ((2001 - 1980) << 9) | (2 << 5) | 3 +EXPECTED_TIME = (4 << 11) | (5 << 5) | (6 // 2) +PAYLOAD = bytes(((index * 43 + 17) & 0xFF) for index in range(4097)) + + +def create(root: pathlib.Path) -> None: + source = root / "MSRC" + target = root / "MDST" + source.mkdir(parents=True) + target.mkdir(parents=True) + path = source / "META.BIN" + path.write_bytes(PAYLOAD) + timestamp = datetime.datetime(2001, 2, 3, 4, 5, 6).timestamp() + os.utime(path, (timestamp, timestamp)) + + +class Fat16: + def __init__(self, raw: bytes) -> None: + self.raw = raw + part = struct.unpack_from(" int: + return self.data + (cluster - 2) * self.cluster_size + + def next_cluster(self, cluster: int) -> int: + return struct.unpack_from(" bytes: + if cluster is None: + return self.raw[self.root:self.root + self.root_entries * 32] + result = bytearray() + while 2 <= cluster < 0xFFF8: + offset = self.cluster_offset(cluster) + result += self.raw[offset:offset + self.cluster_size] + cluster = self.next_cluster(cluster) + return bytes(result) + + @staticmethod + def short_name(name: str) -> bytes: + stem, dot, ext = name.upper().partition(".") + if len(stem) > 8 or len(ext) > 3: + raise ValueError(f"not an 8.3 name: {name}") + return stem.ljust(8).encode("ascii") + ext.ljust(3).encode("ascii") + + def find(self, directory: int | None, name: str) -> bytes | None: + wanted = self.short_name(name) + data = self.directory_bytes(directory) + for offset in range(0, len(data), 32): + entry = data[offset:offset + 32] + if entry[0] == 0x00: + break + if entry[0] == 0xE5 or entry[11] == 0x0F: + continue + if entry[:11] == wanted: + return entry + return None + + @staticmethod + def cluster(entry: bytes) -> int: + return struct.unpack_from(" bytes: + remaining = struct.unpack_from(" tuple[int, int, int]: + return ( + entry[11], + struct.unpack_from(" tuple[Fat16, bytes, bytes | None]: + fat = Fat16(raw_path.read_bytes()) + source_dir = fat.find(None, "MSRC") + target_dir = fat.find(None, "MDST") + if source_dir is None or target_dir is None: + raise RuntimeError("MSRC/MDST отсутствуют в root FAT") + source = fat.find(fat.cluster(source_dir), "META.BIN") + target = fat.find(fat.cluster(target_dir), "META.BIN") + if source is None: + raise RuntimeError("MSRC/META.BIN отсутствует") + return fat, source, target + + +def verify_source(raw_path: pathlib.Path) -> None: + fat, source, target = records(raw_path) + failures: list[str] = [] + if target is not None: + failures.append("MDST не пуст до MAME") + if metadata(source) != (EXPECTED_ATTR, EXPECTED_DATE, EXPECTED_TIME): + failures.append(f"неверные source metadata: {metadata(source)!r}") + if fat.file_bytes(source) != PAYLOAD: + failures.append("source payload повреждён") + if failures: + for failure in failures: + print(f"FAIL: {failure}") + raise SystemExit(1) + print("PASS: source FAT metadata prepared exactly.") + + +def verify(raw_path: pathlib.Path) -> None: + fat, source, target = records(raw_path) + failures: list[str] = [] + if target is None: + failures.append("MDST/META.BIN не создан") + else: + if metadata(target) != metadata(source): + failures.append( + f"metadata differ: src={metadata(source)!r} dst={metadata(target)!r}" + ) + if fat.file_bytes(target) != PAYLOAD: + failures.append("target payload не совпадает") + if metadata(source) != (EXPECTED_ATTR, EXPECTED_DATE, EXPECTED_TIME): + failures.append("source metadata изменены") + if failures: + for failure in failures: + print(f"FAIL: {failure}") + raise SystemExit(1) + print("PASS: F5 preserved exact FAT date/time and R/H/S/A attributes.") + + +def main() -> None: + if len(sys.argv) != 3 or sys.argv[1] not in { + "create", "verify-source", "verify" + }: + raise SystemExit( + f"usage: {sys.argv[0]} create|verify-source|verify PATH" + ) + path = pathlib.Path(sys.argv[2]) + if sys.argv[1] == "create": + create(path) + elif sys.argv[1] == "verify-source": + verify_source(path) + else: + verify(path) + + +if __name__ == "__main__": + main() diff --git a/applications/Volkov/tests/p19_overwrite_fixture.py b/applications/Volkov/tests/p19_overwrite_fixture.py new file mode 100644 index 0000000..157d8c1 --- /dev/null +++ b/applications/Volkov/tests/p19_overwrite_fixture.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +"""Fixture и побайтная проверка транзакционного overwrite P19.""" + +from __future__ import annotations + +import pathlib +import sys + + +def payload(seed: int, size: int) -> bytes: + return bytes(((index * 29 + seed) & 0xFF) for index in range(size)) + + +SOURCES = { + "NORMAL.BIN": payload(11, 4097), + "READONLY.BIN": payload(23, 8193), + "ABSENT.BIN": payload(37, 17), + "COLLIDE.BIN": payload(51, 4096), + "ROLLBACK.BIN": payload(67, 4096), +} +OLD = { + "NORMAL.BIN": b"old-normal-target", + "READONLY.BIN": b"old-readonly-target", + "COLLIDE.BIN": b"old-collision-target", + "ROLLBACK.BIN": b"old-rollback-must-survive", +} +COLLISION = b"pre-existing backup name must survive" + + +def create(root: pathlib.Path) -> None: + source = root / "SOURCE" + target = root / "TARGET" + source.mkdir(parents=True) + target.mkdir(parents=True) + for name, data in SOURCES.items(): + (source / name).write_bytes(data) + for name, data in OLD.items(): + (target / name).write_bytes(data) + (target / "~SB0000.BAK").write_bytes(COLLISION) + + +def verify(root: pathlib.Path) -> None: + target = root / "TARGET" + expected = { + "NORMAL.BIN": SOURCES["NORMAL.BIN"], + "READONLY.BIN": SOURCES["READONLY.BIN"], + "COLLIDE.BIN": SOURCES["COLLIDE.BIN"], + "ABSENT.BIN": SOURCES["ABSENT.BIN"], + "RENAMED.BIN": SOURCES["ABSENT.BIN"], + "ROLLBACK.BIN": OLD["ROLLBACK.BIN"], + "~SB0000.BAK": COLLISION, + } + failures: list[str] = [] + for name, data in expected.items(): + path = target / name + if not path.is_file() or path.read_bytes() != data: + failures.append(f"неверный target {name}") + unexpected = sorted( + path.name for path in target.glob("~SB*.BAK") + if path.name.upper() != "~SB0000.BAK" + ) + if unexpected: + failures.append(f"оставлены служебные backup: {unexpected!r}") + result = root / "RESULT.TXT" + text = result.read_text("ascii", errors="replace") if result.exists() else "" + if "ALL PASS" not in text: + failures.append("target-probe не записал ALL PASS") + if failures: + for failure in failures: + print(f"FAIL: {failure}") + raise SystemExit(1) + print("PASS: transactional overwrite, readonly, collision and rollback.") + + +def main() -> None: + if len(sys.argv) != 3 or sys.argv[1] not in {"create", "verify"}: + raise SystemExit(f"usage: {sys.argv[0]} create|verify PATH") + root = pathlib.Path(sys.argv[2]) + if sys.argv[1] == "create": + create(root) + else: + verify(root) + + +if __name__ == "__main__": + main() diff --git a/applications/Volkov/tests/p19_overwrite_probe.c b/applications/Volkov/tests/p19_overwrite_probe.c new file mode 100644 index 0000000..e9a9565 --- /dev/null +++ b/applications/Volkov/tests/p19_overwrite_probe.c @@ -0,0 +1,121 @@ +/* + * p19_overwrite_probe.c — target-проверка backup/commit/rollback copy-job. + * + * UI-policy проверяется отдельно. Здесь ошибки подаются точно между + * последним write и commit, чтобы доказать сохранность старого target. + */ + +#include +#include +#include +#include +#include +#include +#include "sc_copy.h" + +static ScCopyJob job; +static int result_fd = -1; +static uint8_t failures; + +static void log_text(const char *text) +{ + const char *end = text; + uint16_t length; + + cputs(text); + while (*end != '\0') end++; + length = (uint16_t)(end - text); + if (result_fd >= 0) (void)write(result_fd, text, length); +} + +static void check(uint8_t condition, const char *name) +{ + log_text(condition ? "PASS " : "FAIL "); + if (!condition) failures++; + log_text(name); + log_text("\r\n"); +} + +static int copy_file(const char *source_name, const char *target_name, + uint32_t size, uint8_t page, uint8_t replace) +{ + int step; + + if (sc_copy_begin_policy(&job, "D:\\SOURCE", "D:\\TARGET", + source_name, target_name, size, page, + replace) != 0) return SC_COPY_STEP_ERROR; + do { + step = sc_copy_step(&job); + } while (step == SC_COPY_STEP_MORE); + return step; +} + +int main(void) +{ + uint16_t total_before = 0u; + uint16_t free_before = 0u; + uint16_t total_after = 0u; + uint16_t free_after = 0u; + uint8_t block; + uint8_t page; + int step; + + textattr(0x07u); + clrscr_attr(0x07u); + cputs("P19 transactional overwrite\r\n\r\n"); + result_fd = open("D:\\RESULT.TXT", O_WRONLY | O_CREAT | O_TRUNC); + mem_info(&total_before, &free_before); + block = mem_alloc_pages(1u); + if (block == 0u) return 1; + page = mem_get_page(block, 0u); + + errno = 0; + step = sc_copy_begin(&job, "D:\\SOURCE", "D:\\TARGET", + "NORMAL.BIN", 4097ul, page); + check(step != 0 && job.error == EEXIST, + "default still refuses existing target"); + + step = copy_file("NORMAL.BIN", "NORMAL.BIN", 4097ul, page, 1u); + check(step == SC_COPY_STEP_DONE && job.committed, + "overwrite commits through backup"); + + step = copy_file("READONLY.BIN", "READONLY.BIN", 8193ul, page, 1u); + check(step == SC_COPY_STEP_DONE && job.committed && + (job.existing_attr & 1u), "explicit readonly overwrite"); + + step = copy_file("COLLIDE.BIN", "COLLIDE.BIN", 4096ul, page, 1u); + check(step == SC_COPY_STEP_DONE && job.committed, + "pre-existing backup name skipped"); + + step = copy_file("ABSENT.BIN", "ABSENT.BIN", 17ul, page, 1u); + check(step == SC_COPY_STEP_DONE && job.committed, + "replace policy accepts vanished target"); + + step = copy_file("ABSENT.BIN", "RENAMED.BIN", 17ul, page, 0u); + check(step == SC_COPY_STEP_DONE && job.committed, + "distinct destination name"); + + step = sc_copy_begin_policy(&job, "D:\\SOURCE", "D:\\TARGET", + "ROLLBACK.BIN", "ROLLBACK.BIN", + 4096ul, page, 1u); + if (step == 0) step = sc_copy_step(&job); + if (step == SC_COPY_STEP_MORE) { + (void)close(job.temp_fd); + job.temp_fd = -1; + (void)unlink(job.temp_path); + step = sc_copy_step(&job); + } + check(step == SC_COPY_STEP_ERROR && !job.committed, + "rename failure rolls original target back"); + + mem_free_block(block); + mem_info(&total_after, &free_after); + check(total_before == total_after && free_before == free_after, + "EMM restored"); + if (failures == 0u) log_text("ALL PASS\r\n"); + if (result_fd >= 0) (void)close(result_fd); + cputs("\r\nPress Enter to return to DSS.\r\n"); + while (kbhit()) (void)getkey(); + (void)getkey(); + return failures; +} diff --git a/applications/Volkov/tests/p19_policy_fixture.py b/applications/Volkov/tests/p19_policy_fixture.py new file mode 100644 index 0000000..7554930 --- /dev/null +++ b/applications/Volkov/tests/p19_policy_fixture.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +"""Fixture и проверка UI-policy overwrite/skip/rename/cancel P19.""" + +from __future__ import annotations + +import pathlib +import sys + + +def payload(seed: int, size: int) -> bytes: + return bytes(((index * 31 + seed) & 0xFF) for index in range(size)) + + +SOURCES = { + "AONE.BIN": payload(1, 4097), + "ATWO.BIN": payload(2, 8193), + "CANCEL.BIN": payload(3, 33), + "REN.BIN": payload(4, 257), + "RONLY.BIN": payload(5, 5000), + "SONE.BIN": payload(6, 65), + "STWO.BIN": payload(7, 66), +} +OLD = {name: ("old-" + name).encode("ascii") for name in SOURCES} + + +def create(root: pathlib.Path) -> None: + source = root / "P19SRC" + target = root / "P19DST" + source.mkdir(parents=True) + target.mkdir(parents=True) + for name, data in SOURCES.items(): + (source / name).write_bytes(data) + for name, data in OLD.items(): + (target / name).write_bytes(data) + + +def verify(root: pathlib.Path) -> None: + target = root / "P19DST" + expected = { + "AONE.BIN": SOURCES["AONE.BIN"], + "ATWO.BIN": SOURCES["ATWO.BIN"], + "RONLY.BIN": SOURCES["RONLY.BIN"], + "SONE.BIN": OLD["SONE.BIN"], + "STWO.BIN": OLD["STWO.BIN"], + "REN.BIN": OLD["REN.BIN"], + "CANCEL.BIN": OLD["CANCEL.BIN"], + "NEXT.BIN": SOURCES["REN.BIN"], + } + failures: list[str] = [] + for name, data in expected.items(): + path = target / name + if not path.is_file() or path.read_bytes() != data: + failures.append(f"неверный target {name}") + garbage = sorted( + path.name for path in target.iterdir() + if path.name.upper().startswith(("~SC", "~SB")) + ) + if garbage: + failures.append(f"оставлены служебные файлы: {garbage!r}") + if failures: + for failure in failures: + print(f"FAIL: {failure}") + raise SystemExit(1) + print("PASS: overwrite-all, readonly prompt, skip-all, rename and cancel.") + + +def main() -> None: + if len(sys.argv) != 3 or sys.argv[1] not in {"create", "verify"}: + raise SystemExit(f"usage: {sys.argv[0]} create|verify PATH") + root = pathlib.Path(sys.argv[2]) + if sys.argv[1] == "create": + create(root) + else: + verify(root) + + +if __name__ == "__main__": + main() diff --git a/applications/Volkov/tests/p1_platform/Makefile b/applications/Volkov/tests/p1_platform/Makefile new file mode 100644 index 0000000..dac0137 --- /dev/null +++ b/applications/Volkov/tests/p1_platform/Makefile @@ -0,0 +1,32 @@ +# P1 platform probe для Sprinter Commander. +# +# Собирает два файла: +# p1probe.exe — интерактивные пробы каталогов, EMM, экрана, клавиатуры, +# DSKINFO и EXEC; +# p1child.exe — дочерняя программа для проверки EXEC/возврата. +# +# Пробы намеренно собираются в small: это изолированный диагностический +# инструмент, а не topology будущего Commander. Сборка big проверяется в P2. + +PROJ_ROOT := $(abspath $(CURDIR)/../../../..) +EXAMPLE := p1probe +MEMORY := small +ALLOCS ?= 3000 +EXTRA_FLAGS := --safe --max-allocs $(ALLOCS) +EXTRA_SRCS := p1_sys.c p1_emm.c p1_dir.c p1_screen.c p1_keyboard.c p1_exec_test.c +EXTRA_DATA := p1child.exe + +include $(PROJ_ROOT)/app.mk + +# EXEC-тест ожидает дочерний EXE рядом с основной пробой. +$(EXE): Makefile p1child.exe + +p1child.exe: p1child.c + $(SPRINTER_CC) --memory tiny --safe --max-allocs $(ALLOCS) -o $@ $< + +clean-child: + rm -f p1child.exe + +clean: clean-child + +.PHONY: clean-child diff --git a/applications/Volkov/tests/p1_platform/mame_dump_keys.lua b/applications/Volkov/tests/p1_platform/mame_dump_keys.lua new file mode 100644 index 0000000..62b3238 --- /dev/null +++ b/applications/Volkov/tests/p1_platform/mame_dump_keys.lua @@ -0,0 +1,25 @@ +-- Диагностическая проба полей AT-клавиатуры MAME. +-- Нужна для воспроизводимого ввода навигационных и функциональных клавиш. + +local ready = false +local done = false + +emu.add_machine_reset_notifier(function() + ready = true +end) + +emu.register_periodic(function() + if not ready or done then return end + done = true + + for tag, port in pairs(manager.machine.ioport.ports) do + if tostring(tag):find(":kbd:ms_naturl:P", 1, true) then + for name, field in pairs(port.fields) do + print(string.format("[p1-key] %s mask=0x%02X name=%s", + tostring(tag), field.mask, tostring(name))) + end + end + end + + manager.machine:exit() +end) diff --git a/applications/Volkov/tests/p1_platform/mame_p1_keyboard.lua b/applications/Volkov/tests/p1_platform/mame_p1_keyboard.lua new file mode 100644 index 0000000..485e7cb --- /dev/null +++ b/applications/Volkov/tests/p1_platform/mame_p1_keyboard.lua @@ -0,0 +1,147 @@ +-- Автоматический MAME-сценарий для P1-пробы клавиатуры. +-- Ввод идёт прямо в поля AT-клавиатуры Sprinter, как в +-- toolchain/mame_interactive.py, но дополнен именованными клавишами. + +local KB = ":kbd:ms_naturl:" +local HOLD = 0.06 +local events = {} + +local function add(t, tag, mask, value) + events[#events + 1] = {t, KB .. tag, mask, value} +end + +local function key(t, tag, mask) + add(t, tag, mask, 1) + add(t + HOLD, tag, mask, 0) +end + +local function chord(t, mod_tag, mod_mask, key_tag, key_mask) + add(t, mod_tag, mod_mask, 1) + add(t + 0.02, key_tag, key_mask, 1) + add(t + 0.08, key_tag, key_mask, 0) + add(t + 0.10, mod_tag, mod_mask, 0) +end + +local chars = { + a={"P1.6",0x04}, b={"P1.5",0x10}, e={"P1.4",0x20}, + o={"P2.2",0x02}, p={"P2.3",0x20}, r={"P1.5",0x01}, + x={"P1.2",0x10}, ["1"]={"P1.6",0x01}, + [" "]={"P2.4",0x80}, ["."]={"P2.2",0x10}, + ["\\"]={"P2.1",0x04}, ["\n"]={"P2.1",0x10}, + [":"]={"P2.3",0x02,true} +} + +local function type_text(t, text) + for i = 1, #text do + local ch = text:sub(i, i) + local def = chars[ch] + local shifted = false + if not def then + local lower = ch:lower() + def = chars[lower] + shifted = ch ~= lower + else + shifted = def[3] or false + end + if shifted then + chord(t, "P1.7", 0x02, def[1], def[2]) + else + key(t, def[1], def[2]) + end + t = t + 0.14 + end +end + +-- Запуск P1PROBE.EXE из приглашения DSS и выбор клавиатурной пробы. +type_text(8.0, "a:\\P1PROBE .EXE\n") +key(14.0, "P1.5", 0x40) -- 4 + +-- 24 события: навигация, F1..F10, комбинации Commander и завершающий Esc. +local sequence = { + {"P2.4",0x01}, -- Up + {"P2.3",0x10}, -- Down + {"P2.1",0x08}, -- Left + {"P2.4",0x40}, -- Right + {"P2.5",0x01}, -- Home + {"P2.5",0x20}, -- End + {"P1.2",0x01}, -- Page Up + {"P1.2",0x20}, -- Page Down + {"P2.6",0x01}, -- Insert + {"P2.6",0x02}, -- Delete + {"P1.2",0x40}, -- F1 + {"P1.2",0x80}, -- F2 + {"P2.6",0x40}, -- F3 + {"P2.6",0x80}, -- F4 + {"P2.1",0x40}, -- F5 + {"P2.1",0x80}, -- F6 + {"P2.2",0x40}, -- F7 + {"P2.2",0x80}, -- F8 + {"P2.3",0x40}, -- F9 + {"P2.3",0x80} -- F10 +} + +for i, def in ipairs(sequence) do + key(16.0 + (i - 1) * 0.35, def[1], def[2]) +end +chord(23.00, "P1.1", 0x02, "P1.5", 0x01) -- Ctrl+R +chord(23.35, "P1.1", 0x02, "P1.2", 0x01) -- Ctrl+PgUp +chord(23.70, "P1.3", 0x04, "P1.2", 0x40) -- Alt+F1 +key(24.05, "P1.6", 0x40) -- Esc + +-- Закрыть итоговый экран и подтвердить возврат к главному меню. +key(27.5, "P2.1", 0x10) + +table.sort(events, function(a, b) return a[1] < b[1] end) + +local snap_times = {17.8, 20.6, 23.4, 25.2, 29.0} +local snaps_done = {} +local start_time = nil +local ports = nil +local index = 1 +local field_cache = {} + +local function now() + local t = manager.machine.time + return t.seconds + t.attoseconds / 1e18 +end + +local function field(tag, mask) + local cache_key = tag .. "/" .. mask + if field_cache[cache_key] then return field_cache[cache_key] end + for _, candidate in pairs(ports[tag].fields) do + if candidate.mask == mask then + field_cache[cache_key] = candidate + return candidate + end + end + error("Не найдено поле " .. cache_key) +end + +emu.add_machine_reset_notifier(function() + start_time = now() +end) + +emu.register_periodic(function() + if not start_time then return end + if not ports then ports = manager.machine.ioport.ports end + local elapsed = now() - start_time + + while index <= #events and elapsed >= events[index][1] do + local event = events[index] + field(event[2], event[3]):set_value(event[4]) + index = index + 1 + end + + for _, snap_time in ipairs(snap_times) do + if not snaps_done[snap_time] and elapsed >= snap_time then + snaps_done[snap_time] = true + manager.machine.video:snapshot() + print("[p1-kbd] snapshot t=" .. elapsed) + end + end + + if elapsed >= 31.0 then + print("[p1-kbd] exit t=" .. elapsed) + manager.machine:exit() + end +end) diff --git a/applications/Volkov/tests/p1_platform/p1_dir.c b/applications/Volkov/tests/p1_platform/p1_dir.c new file mode 100644 index 0000000..25171ae --- /dev/null +++ b/applications/Volkov/tests/p1_platform/p1_dir.c @@ -0,0 +1,173 @@ +/* + * p1_dir.c — наблюдаемая проверка CURDIR/CHDIR/F_FIRST/F_NEXT. + * + * Тест не меняет файлы. chdir(".") используется только для проверки + * сохранения текущего каталога. + */ + +#include +#include +#include +#include +#include +#include +#include "p1_platform.h" + +#define P1_FIND_ATTRS (FA_RDONLY | FA_HIDDEN | FA_SYSTEM | FA_DIREC | FA_ARCH) +#define P1_FIND_LIMIT 4096u + +static char cwd_before[256]; +static char cwd_after[256]; +static ffblk_t find_buf; + +static int scan_pattern(const char *pattern, uint16_t *out_count, + uint8_t *out_dot, uint8_t *out_dotdot) +{ + uint16_t count = 0; + uint8_t dot = 0; + uint8_t dotdot = 0; + int rc; + int end_errno; + + errno = 0; + rc = ffirst(pattern, &find_buf, P1_FIND_ATTRS); + if (rc != 0) { + if (errno == ENOENT) { + printf("Pattern %-4s: empty (ffirst errno=%d)\n", pattern, errno); + *out_count = 0; + *out_dot = 0; + *out_dotdot = 0; + return 0; + } + printf("ffirst(%s) failed: errno=%d\n", pattern, errno); + return -1; + } + + for (;;) { + if (strcmp(find_buf.found_name, ".") == 0) dot = 1; + if (strcmp(find_buf.found_name, "..") == 0) dotdot = 1; + + if (count < 20) { + printf(" %02X %10lu %04X/%04X %s\n", + (unsigned)find_buf.found_attr, + (unsigned long)find_buf.size, + (unsigned)find_buf.date, + (unsigned)find_buf.time, + find_buf.found_name); + } + + count++; + if (count >= P1_FIND_LIMIT) { + puts("Enumeration limit reached: possible F_NEXT loop."); + return -1; + } + + errno = 0; + if (fnext(&find_buf) != 0) break; + } + + end_errno = errno; + printf("Pattern %-4s: count=%u dot=%u dotdot=%u end_errno=%d\n", + pattern, (unsigned)count, (unsigned)dot, (unsigned)dotdot, + end_errno); + + /* MAME 0.287 / BIOS 3.06 подтвердил ENOENT (3) для конца F_NEXT. + * Код 0x0F из старого описания DSS здесь не является штатным. */ + if (end_errno != ENOENT) { + puts("Unexpected F_NEXT termination code."); + return -1; + } + + *out_count = count; + *out_dot = dot; + *out_dotdot = dotdot; + return 0; +} + +int p1_test_directory(void) +{ + uint16_t count_star_dot; + uint16_t count_star; + uint8_t dot1, dotdot1, dot2, dotdot2; + int failed = 0; + + p1_heading("Directory: CURDIR, CHDIR, F_FIRST/F_NEXT"); + + errno = 0; + if (getcwd(cwd_before, sizeof(cwd_before)) == 0) { + printf("getcwd failed: errno=%d\n", errno); + return -1; + } + printf("CWD before: %s\n", cwd_before); + + if (chdir(".") != 0) { + printf("chdir(.) failed: errno=%d\n", errno); + failed++; + } + + if (getcwd(cwd_after, sizeof(cwd_after)) == 0) { + printf("second getcwd failed: errno=%d\n", errno); + return -1; + } + printf("CWD after : %s\n", cwd_after); + if (strcmp(cwd_before, cwd_after) != 0) { + puts("CWD changed after chdir(.)."); + failed++; + } + + puts("\nFirst entries for *.*:"); + if (scan_pattern("*.*", &count_star_dot, &dot1, &dotdot1) != 0) { + failed++; + } + + puts("\nFirst entries for *:"); + if (scan_pattern("*", &count_star, &dot2, &dotdot2) != 0) { + failed++; + } + + if (count_star != count_star_dot) { + printf("NOTE: * count (%u) differs from *.* count (%u).\n", + (unsigned)count_star, (unsigned)count_star_dot); + } + if (dot1 != dot2 || dotdot1 != dotdot2) { + puts("NOTE: dot entries differ between patterns."); + } + + printf("Directory checks failed: %u\n", (unsigned)failed); + return failed ? -1 : 0; +} + +int p1_test_disk_info(void) +{ + P1DiskInfo info; + uint32_t cluster_bytes; + uint32_t total_bytes; + uint32_t free_bytes; + + p1_heading("DSKINFO: current disk"); + errno = 0; + if (p1_disk_info(0xFF, &info) != 0) { + printf("DSKINFO failed: errno=%d\n", errno); + return -1; + } + + cluster_bytes = (uint32_t)info.sectors_per_cluster * + (uint32_t)info.bytes_per_sector; + total_bytes = cluster_bytes * (uint32_t)info.total_clusters; + free_bytes = cluster_bytes * (uint32_t)info.free_clusters; + + printf("Sectors/cluster : %u\n", (unsigned)info.sectors_per_cluster); + printf("Bytes/sector : %u\n", (unsigned)info.bytes_per_sector); + printf("Total clusters : %u\n", (unsigned)info.total_clusters); + printf("Free clusters : %u\n", (unsigned)info.free_clusters); + printf("Cluster bytes : %lu\n", (unsigned long)cluster_bytes); + printf("Total bytes : %lu\n", (unsigned long)total_bytes); + printf("Free bytes : %lu\n", (unsigned long)free_bytes); + + if (info.sectors_per_cluster == 0 || info.bytes_per_sector == 0 || + info.total_clusters == 0 || info.free_clusters > info.total_clusters) { + puts("DSKINFO returned inconsistent values."); + return -1; + } + return 0; +} diff --git a/applications/Volkov/tests/p1_platform/p1_emm.c b/applications/Volkov/tests/p1_platform/p1_emm.c new file mode 100644 index 0000000..2844a1c --- /dev/null +++ b/applications/Volkov/tests/p1_platform/p1_emm.c @@ -0,0 +1,96 @@ +/* + * p1_emm.c — проверка выделения трёх EMM-страниц и доступа через W3. + */ + +#include +#include +#include +#include +#include +#include "p1_platform.h" + +#define P1_PAGE_SIZE 0x4000u +#define P1_BLOCK 64u + +static uint8_t write_buf[P1_BLOCK]; +static uint8_t read_buf[P1_BLOCK]; + +static void make_pattern(uint8_t page_index, uint8_t salt) +{ + uint8_t i; + for (i = 0; i < P1_BLOCK; i++) { + write_buf[i] = (uint8_t)(salt ^ (uint8_t)(page_index * 0x41u + i)); + read_buf[i] = 0; + } +} + +static int check_region(uint8_t page, uint8_t page_index, + uint16_t offset, uint8_t salt) +{ + make_pattern(page_index, salt); + bank_write(page, offset, write_buf, P1_BLOCK); + bank_read(page, offset, read_buf, P1_BLOCK); + if (memcmp(write_buf, read_buf, P1_BLOCK) != 0) { + printf("Mismatch: logical=%u phys=%02X offset=%04X\n", + (unsigned)page_index, (unsigned)page, (unsigned)offset); + return -1; + } + return 0; +} + +int p1_test_emm(void) +{ + uint16_t total_before, free_before; + uint16_t total_after, free_after; + uint8_t block; + uint8_t page[3]; + uint8_t i; + int failed = 0; + + p1_heading("EMM: 3-page allocation and W3 access"); + errno = 0; + mem_info(&total_before, &free_before); + printf("Before: total=%u free=%u pages\n", + (unsigned)total_before, (unsigned)free_before); + + block = mem_alloc_pages(3); + if (block == 0) { + printf("mem_alloc_pages(3) failed: errno=%d\n", errno); + return -1; + } + + printf("Block id: %u\n", (unsigned)block); + for (i = 0; i < 3; i++) { + page[i] = mem_get_page(block, i); + printf(" logical %u -> physical %02X\n", + (unsigned)i, (unsigned)page[i]); + if (page[i] == 0) failed++; + } + + if (page[0] == page[1] || page[0] == page[2] || page[1] == page[2]) { + puts("Physical page numbers are not distinct."); + failed++; + } + + for (i = 0; i < 3; i++) { + if (page[i] == 0) continue; + if (check_region(page[i], i, 0, 0x5A) != 0) failed++; + if (check_region(page[i], i, + (uint16_t)(P1_PAGE_SIZE - P1_BLOCK), 0xA5) != 0) { + failed++; + } + } + + mem_free_block(block); + mem_info(&total_after, &free_after); + printf("After : total=%u free=%u pages\n", + (unsigned)total_after, (unsigned)free_after); + + if (total_before != total_after || free_before != free_after) { + puts("EMM counters were not restored after free."); + failed++; + } + + printf("EMM checks failed: %u\n", (unsigned)failed); + return failed ? -1 : 0; +} diff --git a/applications/Volkov/tests/p1_platform/p1_exec_test.c b/applications/Volkov/tests/p1_platform/p1_exec_test.c new file mode 100644 index 0000000..559eab2 --- /dev/null +++ b/applications/Volkov/tests/p1_platform/p1_exec_test.c @@ -0,0 +1,116 @@ +/* + * p1_exec_test.c — проверка EXEC, возврата к родителю и WAIT. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "p1_platform.h" + +typedef struct { + int rc; + int error; + uint8_t exit_code; + uint8_t wait_code; + uint8_t page_w1; + uint8_t page_w2; + uint8_t page_w3; + char cwd[256]; +} P1ExecResult; + +static P1ExecResult short_result; +static P1ExecResult path_result; +static char cwd_before[256]; + +static void run_exec_case(const char *label, const char *path, + uint8_t path_mode, P1ExecResult *result) +{ + p1_heading(label); + printf("Calling EXEC: B=%u, path=%s\n", (unsigned)path_mode, path); + cputs("The child switches to 40x32 and waits for one key.\r\n"); + cputs("After that it returns code 0x5A.\r\n\r\n"); + cputs("Press any key to call EXEC..."); + (void)getkey(); + + errno = 0; + result->rc = p1_exec(path_mode, path); + result->error = errno; + result->exit_code = p1_exec_exit_code; + result->wait_code = p1_wait_code(); + + /* Сначала снять страницы, затем исправлять видеорежим и печатать. */ + result->page_w1 = _io_page_w1; + result->page_w2 = _io_page_w2; + result->page_w3 = _io_page_w3; + + if (getcwd(result->cwd, sizeof(result->cwd)) == 0) { + result->cwd[0] = 0; + } + (void)settextmode(TEXT_MODE_80x32); +} + +static void print_result(const char *name, const P1ExecResult *result) +{ + printf("%s: rc=%d errno=%d exit=%02X wait=%02X pages=%02X/%02X/%02X\n", + name, result->rc, result->error, + (unsigned)result->exit_code, (unsigned)result->wait_code, + (unsigned)result->page_w1, (unsigned)result->page_w2, + (unsigned)result->page_w3); + printf(" cwd=%s\n", result->cwd[0] ? result->cwd : ""); +} + +int p1_test_exec(void) +{ + uint8_t before_w1 = _io_page_w1; + uint8_t before_w2 = _io_page_w2; + uint8_t before_w3 = _io_page_w3; + int failed = 0; + + if (getcwd(cwd_before, sizeof(cwd_before)) == 0) { + p1_heading("EXEC: setup failed"); + printf("getcwd failed: errno=%d\n", errno); + return -1; + } + + /* B=0 характеризуем для будущей командной строки. Ошибка этого случая + * не блокирует PoC: Commander будет передавать явный путь с B=1. */ + run_exec_case("EXEC case 1: short name / PATH", + "P1CHILD.EXE", 0, &short_result); + + /* B=1 и явный относительный путь — обязательный случай PoC. */ + run_exec_case("EXEC case 2: explicit path", + ".\\P1CHILD.EXE", 1, &path_result); + + p1_heading("EXEC results"); + printf("Parent before: pages=%02X/%02X/%02X cwd=%s\n", + (unsigned)before_w1, (unsigned)before_w2, (unsigned)before_w3, + cwd_before); + print_result("B=0", &short_result); + print_result("B=1", &path_result); + + if (path_result.rc != 0 || path_result.exit_code != 0x5A || + path_result.wait_code != 0x5A) { + puts("Required B=1 EXEC result is incorrect."); + failed++; + } + if (path_result.page_w1 != before_w1 || path_result.page_w2 != before_w2 || + path_result.page_w3 != before_w3) { + puts("Parent window mapping was not restored."); + failed++; + } + if (strcmp(path_result.cwd, cwd_before) != 0) { + puts("Parent CWD changed after child."); + failed++; + } + + if (short_result.rc != 0) { + puts("NOTE: B=0 short-name lookup failed; explicit B=1 remains usable."); + } + + printf("EXEC checks failed: %u\n", (unsigned)failed); + return failed ? -1 : 0; +} diff --git a/applications/Volkov/tests/p1_platform/p1_keyboard.c b/applications/Volkov/tests/p1_platform/p1_keyboard.c new file mode 100644 index 0000000..34ff3dc --- /dev/null +++ b/applications/Volkov/tests/p1_platform/p1_keyboard.c @@ -0,0 +1,45 @@ +/* + * p1_keyboard.c — печать сырых событий getkey()/kbd_mod_state(). + */ + +#include +#include +#include +#include "p1_platform.h" + +static uint8_t compact_modifiers(uint16_t state) +{ + uint8_t result = 0; + if (state & (KBD_MOD_LSHIFT | KBD_MOD_RSHIFT)) result |= 0x01; + if (state & (KBD_MOD_CTRL | KBD_MOD_LCTRL | KBD_MOD_RCTRL)) result |= 0x02; + if (state & (KBD_MOD_ALT | KBD_MOD_LALT | KBD_MOD_RALT)) result |= 0x04; + return result; +} + +int p1_test_keyboard(void) +{ + uint8_t n = 0; + + p1_heading("Keyboard: raw event capture"); + cputs("Press navigation keys, F1..F10 and Ctrl combinations.\r\n"); + cputs("Esc finishes after its event is printed. Maximum: 24 events.\r\n\r\n"); + cputs(" n key ascii scan live_mod compact\r\n"); + + while (n < 24) { + uint16_t key = getkey(); + uint16_t live = kbd_mod_state(); + uint8_t ascii = (uint8_t)key; + uint8_t scan = (uint8_t)(key >> 8); + uint8_t compact = compact_modifiers(live); + char printable = (ascii >= 0x20 && ascii < 0x7F) ? (char)ascii : '.'; + + printf("%2u %c %02X %02X %04X %02X\n", + (unsigned)n, printable, (unsigned)ascii, (unsigned)scan, + (unsigned)live, (unsigned)compact); + n++; + if (ascii == 0x1B) break; + } + + cputs("\r\nCompare the captured values with commander-keymap.md.\r\n"); + return 0; +} diff --git a/applications/Volkov/tests/p1_platform/p1_platform.h b/applications/Volkov/tests/p1_platform/p1_platform.h new file mode 100644 index 0000000..3a279cd --- /dev/null +++ b/applications/Volkov/tests/p1_platform/p1_platform.h @@ -0,0 +1,43 @@ +/* + * p1_platform.h — общий контракт платформенных проб Commander P1. + * + * Это диагностический код приложения, не публичный заголовок libc. После + * подтверждения в MAME стабильные wrappers либо останутся в Commander, + * либо будут перенесены в libc отдельными модулями и тестами. + */ + +#ifndef P1_PLATFORM_H +#define P1_PLATFORM_H + +#include + +typedef struct { + uint8_t sectors_per_cluster; + uint16_t total_clusters; + uint16_t free_clusters; + uint16_t bytes_per_sector; +} P1DiskInfo; + +typedef char P1DiskInfo_must_be_7_bytes[(sizeof(P1DiskInfo) == 7) ? 1 : -1]; + +/* Последний код завершения, возвращённый EXEC при CF=0. */ +extern uint8_t p1_exec_exit_code; + +int p1_disk_info(uint8_t disk, P1DiskInfo *out); +int p1_exec(uint8_t path_mode, const char *path); +uint8_t p1_wait_code(void); + +void p1_winrest(uint8_t row, uint8_t col, uint8_t height, uint8_t width, + uint8_t page, uint16_t offset); + +int p1_test_emm(void); +int p1_test_directory(void); +int p1_test_screen(void); +int p1_test_keyboard(void); +int p1_test_exec(void); +int p1_test_disk_info(void); + +void p1_heading(const char *title); +void p1_pause(void); + +#endif diff --git a/applications/Volkov/tests/p1_platform/p1_screen.c b/applications/Volkov/tests/p1_platform/p1_screen.c new file mode 100644 index 0000000..fc1e411 --- /dev/null +++ b/applications/Volkov/tests/p1_platform/p1_screen.c @@ -0,0 +1,120 @@ +/* + * p1_screen.c — визуальная проверка экранной EMM-страницы и WINREST. + */ + +#include +#include +#include +#include +#include +#include +#include "p1_platform.h" + +#define P1_SCREEN_W 80u +#define P1_SCREEN_H 32u +#define P1_ROW_BYTES 160u +#define P1_MARKER_OFF 0x2000u + +static uint8_t row_buf[P1_ROW_BYTES]; +static uint8_t marker_buf[2u * 20u * 2u]; + +static void row_clear(uint8_t attr) +{ + uint8_t x; + for (x = 0; x < P1_SCREEN_W; x++) { + row_buf[(uint16_t)x * 2u] = ' '; + row_buf[(uint16_t)x * 2u + 1u] = attr; + } +} + +static void row_text(uint8_t x, const char *text, uint8_t attr) +{ + while (*text && x < P1_SCREEN_W) { + row_buf[(uint16_t)x * 2u] = (uint8_t)*text++; + row_buf[(uint16_t)x * 2u + 1u] = attr; + x++; + } +} + +static void marker_build(void) +{ + static const char top[] = "PARTIAL WINREST OK "; + static const char bot[] = "offset = 0x2000 "; + uint8_t x; + + for (x = 0; x < 20; x++) { + marker_buf[(uint16_t)x * 2u] = (uint8_t)top[x]; + marker_buf[(uint16_t)x * 2u + 1u] = + COLOR(COLOR_WHITE, COLOR_RED); + marker_buf[40u + (uint16_t)x * 2u] = (uint8_t)bot[x]; + marker_buf[40u + (uint16_t)x * 2u + 1u] = + COLOR(COLOR_YELLOW, COLOR_RED); + } +} + +int p1_test_screen(void) +{ + uint8_t block; + uint8_t page; + uint8_t y; + + p1_heading("WINREST: preparing EMM screen"); + + errno = 0; + block = mem_alloc_pages(1); + if (block == 0) { + printf("mem_alloc_pages failed: errno=%d\n", errno); + return -1; + } + page = mem_get_page(block, 0); + if (page == 0) { + printf("mem_get_page failed: errno=%d\n", errno); + mem_free_block(block); + return -1; + } + + for (y = 0; y < P1_SCREEN_H; y++) { + /* Только яркие foreground 9..15 на синем: граница не должна + * исчезать из-за комбинации blue-on-blue вроде прежнего 0x11. */ + uint8_t attr = COLOR((uint8_t)(COLOR_LIGHTBLUE + (y % 7u)), + COLOR_BLUE); + row_clear(attr); + row_buf[0] = (y == 0 || y == 31) ? '+' : '|'; + row_buf[158] = (y == 0 || y == 31) ? '+' : '|'; + + if (y == 0 || y == 31) { + uint8_t x; + for (x = 1; x < 79; x++) row_buf[(uint16_t)x * 2u] = '-'; + } + if (y == 2) row_text(4, "FULL 80x32 WINREST FROM EMM PAGE", + COLOR(COLOR_WHITE, COLOR_BLUE)); + if (y == 4) row_text(4, "Rows have different foreground colours.", + COLOR(COLOR_YELLOW, COLOR_BLUE)); + if (y == 6) row_text(4, "Expected: complete border, no shifted cells.", + COLOR(COLOR_YELLOW, COLOR_BLUE)); + if (y == 28) row_text(4, "Press a key for partial-offset test.", + COLOR(COLOR_WHITE, COLOR_BLUE)); + + bank_write(page, (uint16_t)y * P1_ROW_BYTES, + row_buf, P1_ROW_BYTES); + } + + marker_build(); + bank_write(page, P1_MARKER_OFF, marker_buf, sizeof(marker_buf)); + + p1_winrest(0, 0, P1_SCREEN_H, P1_SCREEN_W, page, 0); + (void)getkey(); + + /* Две строки по 20 ячеек поверх середины полного экрана. */ + p1_winrest(14, 30, 2, 20, page, P1_MARKER_OFF); + (void)getkey(); + + mem_free_block(block); + p1_heading("WINREST test finished"); + cputs("Visual checks:\r\n"); + cputs(" 1. Full 80x32 frame had no shifts or holes.\r\n"); + cputs(" 2. Red/yellow 20x2 marker appeared at row 14, col 30.\r\n"); + cputs(" 3. Marker text started at offset 0x2000.\r\n"); + cputs("\r\nReturn PASS only records that both calls completed.\r\n"); + return 0; +} diff --git a/applications/Volkov/tests/p1_platform/p1_sys.c b/applications/Volkov/tests/p1_platform/p1_sys.c new file mode 100644 index 0000000..6c040af --- /dev/null +++ b/applications/Volkov/tests/p1_platform/p1_sys.c @@ -0,0 +1,143 @@ +/* + * p1_sys.c — отсутствующие в libc сырые ESTEX wrappers для проб P1. + * + * ABI SDCC __sdcccall(1): int возвращается в DE, uint8_t — в A. Каждый RST + * сохраняет IX, потому что IX callee-saved. Ошибка ESTEX (CF=1, код в A) + * проходит через общий libc-хелпер __errno_set. + */ + +#include +#include "p1_platform.h" + +uint8_t p1_exec_exit_code; + +/* ESTEX DSKINFO $03: + * A=disk (0xFF=current) + * out: A=sectors/cluster, HL=total clusters, + * DE=free clusters, BC=bytes/sector. + */ +int p1_disk_info(uint8_t disk, P1DiskInfo *out) __naked +{ + (void)disk; + (void)out; + __asm + ;; A = disk, DE = out. + push ix + push de + ld c, #0x03 + rst #0x10 + jr c, _p1_di_error + + ;; Забрать out в IX, пока все выходные регистры DSS ещё целы. + pop ix + ld 0 (ix), a + ld 1 (ix), l + ld 2 (ix), h + ld 3 (ix), e + ld 4 (ix), d + ld 5 (ix), c + ld 6 (ix), b + pop ix + ld de, #0 + ret + + _p1_di_error: + pop de ; снять сохранённый out + pop ix + call __errno_set + ld de, #-1 + ret + __endasm; +} + +/* ESTEX EXEC $40. path_mode: 0 — короткое имя/PATH, 1 — передан путь. + * На успехе код дочерней программы сохраняется в p1_exec_exit_code. + */ +int p1_exec(uint8_t path_mode, const char *path) __naked +{ + (void)path_mode; + (void)path; + __asm + ;; A = path_mode, DE = path. Такой порядок не оставляет 8-битный + ;; второй аргумент на стеке в ABI SDCC. + push ix + ld b, a + ex de, hl + ld c, #0x40 + rst #0x10 + pop ix + jr c, _p1_exec_error + ld (_p1_exec_exit_code), a + ld de, #0 + ret + + _p1_exec_error: + call __errno_set + ld de, #-1 + ret + __endasm; +} + +/* ESTEX WAIT $42 возвращает код последней завершившейся программы в A. */ +uint8_t p1_wait_code(void) __naked +{ + __asm + push ix + ld c, #0x42 + rst #0x10 + pop ix + ret + __endasm; +} + +/* ESTEX WINREST $5A: + * D=row, E=col, H=height, L=width, B=physical page, + * IX=0xC000+offset. + * + * Раскладка аргументов подтверждена существующим tests/winrest и mdview2: + * A=row, L=col, стек +2 height, +3 width, +4 page, +5..6 offset. + */ +void p1_winrest(uint8_t row, uint8_t col, uint8_t height, uint8_t width, + uint8_t page, uint16_t offset) __naked +{ + (void)row; + (void)col; + (void)height; + (void)width; + (void)page; + (void)offset; + __asm + ld iy, #2 + add iy, sp + ld d, a + ld e, l + + push ix + ld l, 3 (iy) + ld h, 4 (iy) + ld bc, #0xC000 + add hl, bc + push hl + pop ix + + ld h, 0 (iy) + ld a, 1 (iy) + ld l, a + ld a, 2 (iy) + ld b, a + ld c, #0x5A + di + rst #0x10 + ei + pop ix + + ;; Снять 5 стековых байтов height,width,page,offset. + pop hl + inc sp + inc sp + inc sp + inc sp + inc sp + jp (hl) + __endasm; +} diff --git a/applications/Volkov/tests/p1_platform/p1child.c b/applications/Volkov/tests/p1_platform/p1child.c new file mode 100644 index 0000000..056f133 --- /dev/null +++ b/applications/Volkov/tests/p1_platform/p1child.c @@ -0,0 +1,35 @@ +/* + * p1child.c — дочерняя программа для проверки ESTEX EXEC. + * + * Намеренно меняет видеорежим, ничего не пишет на диск и возвращает 0x5A. + */ + +#include +#include +#include +#include +#include + +static char child_cwd[256]; + +int main(void) +{ + (void)settextmode(TEXT_MODE_40x32); + clrscr_attr(0x27); + gotoxy(0, 0); + textattr(0x2F); + cputs("P1CHILD.EXE\r\n\r\n"); + textattr(0x27); + cputs("EXEC reached child successfully.\r\n"); + cprintf("W1/W2/W3: %02X/%02X/%02X\r\n", + (unsigned)_io_page_w1, (unsigned)_io_page_w2, + (unsigned)_io_page_w3); + if (getcwd(child_cwd, sizeof(child_cwd)) != 0) { + cprintf("CWD: %s\r\n", child_cwd); + } else { + cputs("CWD: \r\n"); + } + cputs("\r\nPress any key to return 0x5A..."); + (void)getkey(); + return 0x5A; +} diff --git a/applications/Volkov/tests/p1_platform/p1probe.c b/applications/Volkov/tests/p1_platform/p1probe.c new file mode 100644 index 0000000..d53e2d3 --- /dev/null +++ b/applications/Volkov/tests/p1_platform/p1probe.c @@ -0,0 +1,117 @@ +/* + * p1probe.c — интерактивный набор платформенных проб Sprinter Commander. + * + * Программа ничего не удаляет и не изменяет в текущем каталоге. EMM-тест + * пишет исключительно в выделенный блок памяти, дочерняя программа также + * не выполняет дисковых записей. + */ + +#include +#include +#include +#include "p1_platform.h" + +void p1_heading(const char *title) +{ + clrscr_attr(0x17); + gotoxy(0, 0); + textattr(0x1F); + cprintf("Sprinter Commander - P1 platform probe\r\n"); + textattr(0x17); + cprintf("%s\r\n\r\n", title); +} + +void p1_pause(void) +{ + textattr(0x1E); + cputs("\r\nPress any key to return to menu..."); + (void)getkey(); +} + +static void show_menu(void) +{ + p1_heading("Main menu"); + cputs("1 Directory and F_FIRST/F_NEXT\r\n"); + cputs("2 EMM allocation and page access\r\n"); + cputs("3 WINREST full/partial screen\r\n"); + cputs("4 Keyboard event codes\r\n"); + cputs("5 EXEC child and return\r\n"); + cputs("6 DSKINFO current disk\r\n"); + cputs("A Automatic non-visual probes (1, 2, 6)\r\n"); + cputs("0 Exit\r\n\r\n"); + textattr(0x1E); + cputs("Select: "); +} + +static void report_result(int rc) +{ + if (rc == 0) { + textattr(0x2F); + cputs("\r\nRESULT: PASS\r\n"); + } else { + textattr(0x4F); + cputs("\r\nRESULT: FAIL\r\n"); + } +} + +static void run_automatic(void) +{ + int failed = 0; + + p1_heading("Automatic non-visual probes"); + cputs("[1/3] Directory\r\n"); + if (p1_test_directory() != 0) failed++; + + cputs("\r\n[2/3] EMM\r\n"); + if (p1_test_emm() != 0) failed++; + + cputs("\r\n[3/3] DSKINFO\r\n"); + if (p1_test_disk_info() != 0) failed++; + + textattr(failed ? 0x4F : 0x2F); + cprintf("\r\nAutomatic result: %u failed\r\n", (unsigned)failed); + p1_pause(); +} + +int main(void) +{ + uint16_t key; + uint8_t ch; + int rc; + + if (settextmode(TEXT_MODE_80x32) != 0) { + puts("Cannot set 80x32 text mode."); + return 1; + } + + for (;;) { + show_menu(); + key = getkey(); + ch = (uint8_t)key; + if (ch >= 'a' && ch <= 'z') ch = (uint8_t)(ch - 'a' + 'A'); + + if (ch == '0' || ch == 0x1B) break; + if (ch == 'A') { + run_automatic(); + continue; + } + + rc = -1; + switch (ch) { + case '1': rc = p1_test_directory(); break; + case '2': rc = p1_test_emm(); break; + case '3': rc = p1_test_screen(); break; + case '4': rc = p1_test_keyboard(); break; + case '5': rc = p1_test_exec(); break; + case '6': rc = p1_test_disk_info(); break; + default: continue; + } + + report_result(rc); + p1_pause(); + } + + textattr(0x0F); + clrscr(); + return 0; +} diff --git a/applications/Volkov/tests/p2_palette/Makefile b/applications/Volkov/tests/p2_palette/Makefile new file mode 100644 index 0000000..1a4956d --- /dev/null +++ b/applications/Volkov/tests/p2_palette/Makefile @@ -0,0 +1,11 @@ +# P2.2 — отдельная проба системных BIOS/ESTEX-вызовов текстовой палитры. + +PROJ_ROOT := $(abspath $(CURDIR)/../../../..) +EXAMPLE := p2palette +MEMORY := big +ALLOCS ?= 3000 +EXTRA_FLAGS := --safe --max-allocs $(ALLOCS) + +include $(PROJ_ROOT)/app.mk + +$(EXE): Makefile diff --git a/applications/Volkov/tests/p2_palette/p2palette.c b/applications/Volkov/tests/p2_palette/p2palette.c new file mode 100644 index 0000000..da63b01 --- /dev/null +++ b/applications/Volkov/tests/p2_palette/p2palette.c @@ -0,0 +1,171 @@ +/* + * p2palette — минимальная проба многоцветной текстовой темы P2.2. + * + * Использует публичные системные text_pal_get/set_color(). Для каждого + * применяемого атрибута обычный и FLASH-планы задаются попарно одинаково. + * Исходные записи сохраняются и восстанавливаются перед выходом. + */ + +#include +#include + +#define SAMPLE_COUNT 6u +#define ATTR_UI COLOR(COLOR_BLACK, COLOR_CYAN) + +typedef struct { + uint8_t attr; + uint8_t paper_r; + uint8_t paper_g; + uint8_t paper_b; + uint8_t ink_r; + uint8_t ink_g; + uint8_t ink_b; + const char *name; +} ThemeSample; + +static const ThemeSample samples[SAMPLE_COUNT] = { + { COLOR(COLOR_WHITE, COLOR_BLUE), 0x00, 0x00, 0xA8, + 0xFF, 0xFF, 0xFF, "WHITE / BLUE" }, + { COLOR(COLOR_YELLOW, COLOR_GREEN), 0x00, 0xA8, 0x00, + 0xFF, 0xFF, 0x00, "YELLOW / GREEN" }, + { COLOR(COLOR_WHITE, COLOR_RED), 0xA8, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, "WHITE / RED" }, + { COLOR(COLOR_WHITE, COLOR_MAGENTA), 0xA8, 0x00, 0xA8, + 0xFF, 0xFF, 0xFF, "WHITE / MAGENTA" }, + { COLOR(COLOR_BLACK, COLOR_LIGHTGRAY),0xC0, 0xC0, 0xC0, + 0x00, 0x00, 0x00, "BLACK / LIGHTGRAY" }, + { ATTR_UI, 0x00, 0xA8, 0xA8, + 0x00, 0x00, 0x00, "BLACK / CYAN" } +}; + +/* [sample][plane][RGB]. Static: буферы BIOS остаются в W2, не на стеке. */ +static uint8_t saved[SAMPLE_COUNT][4u][3u]; +static uint8_t readback[SAMPLE_COUNT][4u][3u]; + +static void save_palette(void) +{ + uint8_t i; + uint8_t p; + for (i = 0u; i < SAMPLE_COUNT; i++) { + for (p = 0u; p < 4u; p++) { + text_pal_get_color(p, samples[i].attr, + &saved[i][p][0], &saved[i][p][1], + &saved[i][p][2]); + } + } +} +static void set_sample(uint8_t i) +{ + const ThemeSample *s = &samples[i]; + text_pal_set_color(TEXT_PAL_PAPER, s->attr, + s->paper_r, s->paper_g, s->paper_b); + text_pal_set_color(TEXT_PAL_BLINK_PAPER, s->attr, + s->paper_r, s->paper_g, s->paper_b); + text_pal_set_color(TEXT_PAL_INK, s->attr, + s->ink_r, s->ink_g, s->ink_b); + text_pal_set_color(TEXT_PAL_BLINK_INK, s->attr, + s->ink_r, s->ink_g, s->ink_b); +} + +static uint8_t verify_palette(void) +{ + uint8_t i; + uint8_t p; + uint8_t failed = 0u; + + for (i = 0u; i < SAMPLE_COUNT; i++) { + for (p = 0u; p < 4u; p++) { + const ThemeSample *s = &samples[i]; + uint8_t want_r = (p == TEXT_PAL_PAPER || + p == TEXT_PAL_BLINK_PAPER) ? + s->paper_r : s->ink_r; + uint8_t want_g = (p == TEXT_PAL_PAPER || + p == TEXT_PAL_BLINK_PAPER) ? + s->paper_g : s->ink_g; + uint8_t want_b = (p == TEXT_PAL_PAPER || + p == TEXT_PAL_BLINK_PAPER) ? + s->paper_b : s->ink_b; + + text_pal_get_color(p, s->attr, + &readback[i][p][0], &readback[i][p][1], + &readback[i][p][2]); + if (readback[i][p][0] != want_r || + readback[i][p][1] != want_g || + readback[i][p][2] != want_b) failed++; + } + } + return failed; +} + +static void restore_palette(void) +{ + uint8_t i; + uint8_t p; + for (i = 0u; i < SAMPLE_COUNT; i++) { + for (p = 0u; p < 4u; p++) { + text_pal_set_color(p, samples[i].attr, + saved[i][p][0], saved[i][p][1], + saved[i][p][2]); + } + } +} + +static void fill_row(uint8_t y, uint8_t attr, const char *name) +{ + uint8_t x; + for (x = 0u; x < 80u; x++) wrchar(x, y, ' ', attr); + for (x = 0u; name[x] && x < 30u; x++) { + wrchar((uint8_t)(4u + x), y, name[x], attr); + } + for (x = 42u; x < 76u; x++) { + wrchar(x, y, (x & 1u) ? '#' : '@', attr); + } +} + +static void draw_screen(uint8_t failed) +{ + uint8_t i; + + clrscr_attr(ATTR_UI); + textattr(ATTR_UI); + gotoxy(1u, 0u); + cputs("P2.2 SYSTEM TEXT PALETTE PROBE - BIOS CALLS ARE ALLOWED"); + gotoxy(1u, 2u); + cprintf("Readback of 6 attrs x 4 planes: %s (%u mismatches)", + failed ? "FAIL" : "PASS", (unsigned)failed); + gotoxy(1u, 3u); + cputs("Each PAPER pair and INK pair is identical; scene must not flash."); + + for (i = 0u; i < SAMPLE_COUNT; i++) { + fill_row((uint8_t)(6u + i * 3u), samples[i].attr, samples[i].name); + fill_row((uint8_t)(7u + i * 3u), samples[i].attr, + "same attribute, second row"); + } + + gotoxy(1u, 26u); + cputs("If any coloured band disappears, compare MAME phase with readback."); + gotoxy(1u, 28u); + cputs("Press a key to restore every saved palette entry and exit."); +} + +int main(void) +{ + uint8_t previous_mode = gettextmode(); + uint8_t i; + uint8_t failed; + + if (settextmode(TEXT_MODE_80x32) != 0) return 1; + save_palette(); + for (i = 0u; i < SAMPLE_COUNT; i++) set_sample(i); + failed = verify_palette(); + draw_screen(failed); + (void)getkey(); + restore_palette(); + textattr(COLOR(COLOR_WHITE, COLOR_BLACK)); + clrscr(); + if (previous_mode == TEXT_MODE_40x32 || + previous_mode == TEXT_MODE_80x32) { + (void)settextmode(previous_mode); + } + return failed ? 2 : 0; +} diff --git a/applications/Volkov/tests/p2_screen/Makefile b/applications/Volkov/tests/p2_screen/Makefile new file mode 100644 index 0000000..faf6d73 --- /dev/null +++ b/applications/Volkov/tests/p2_screen/Makefile @@ -0,0 +1,15 @@ +# P2.1 — координатный экранный backend Sprinter Commander. +# +# Тест использует системные ESTEX-функции WINREST/RDCHAR/SCROLL, не +# открывает BIOS-окна и не обращается к текстовой VRAM напрямую. + +PROJ_ROOT := $(abspath $(CURDIR)/../../../..) +EXAMPLE := p2screen +MEMORY := big +ALLOCS ?= 3000 +EXTRA_FLAGS := --safe --max-allocs $(ALLOCS) +EXTRA_SRCS := p2_platform.c + +include $(PROJ_ROOT)/app.mk + +$(EXE): Makefile p2_screen.h sc_glyphs.h diff --git a/applications/Volkov/tests/p2_screen/p2_platform.c b/applications/Volkov/tests/p2_screen/p2_platform.c new file mode 100644 index 0000000..849e2d3 --- /dev/null +++ b/applications/Volkov/tests/p2_screen/p2_platform.c @@ -0,0 +1,54 @@ +/* + * p2_platform.c — пакетный координатный вывод screen model через ESTEX. + * + * WINREST 5Ah не принимает идентификатор BIOS-окна: D/E/H/L задают + * абсолютный прямоугольник активной текстовой страницы, B:IX — источник. + * ABI SDCC __sdcccall(1) подтверждён P1-пробой. + */ + +#include +#include "p2_screen.h" + +void p2_winrest(uint8_t row, uint8_t col, uint8_t height, uint8_t width, + uint8_t page, uint16_t offset) __naked +{ + (void)row; + (void)col; + (void)height; + (void)width; + (void)page; + (void)offset; + __asm + ld iy, #2 + add iy, sp + ld d, a + ld e, l + + push ix + ld l, 3 (iy) + ld h, 4 (iy) + ld bc, #0xC000 + add hl, bc + push hl + pop ix + + ld h, 0 (iy) + ld a, 1 (iy) + ld l, a + ld a, 2 (iy) + ld b, a + ld c, #0x5A + di + rst #0x10 + ei + pop ix + + pop hl + inc sp + inc sp + inc sp + inc sp + inc sp + jp (hl) + __endasm; +} diff --git a/applications/Volkov/tests/p2_screen/p2_screen.h b/applications/Volkov/tests/p2_screen/p2_screen.h new file mode 100644 index 0000000..85379d6 --- /dev/null +++ b/applications/Volkov/tests/p2_screen/p2_screen.h @@ -0,0 +1,26 @@ +/* + * p2_screen.h — общий контракт координатной экранной пробы P2.1. + */ + +#ifndef P2_SCREEN_API_H +#define P2_SCREEN_API_H + +#include + +#define P2_SCREEN_W 80u +#define P2_SCREEN_H 32u +#define P2_ROW_BYTES 160u +#define P2_MODEL_BYTES 5120u +#define P2_MARKER_OFF 0x2000u + +typedef struct { + uint8_t ch; + uint8_t attr; +} P2Cell; + +typedef char P2Cell_must_be_2_bytes[(sizeof(P2Cell) == 2) ? 1 : -1]; + +void p2_winrest(uint8_t row, uint8_t col, uint8_t height, uint8_t width, + uint8_t page, uint16_t offset); + +#endif diff --git a/applications/Volkov/tests/p2_screen/p2screen.c b/applications/Volkov/tests/p2_screen/p2screen.c new file mode 100644 index 0000000..f2c47c8 --- /dev/null +++ b/applications/Volkov/tests/p2_screen/p2screen.c @@ -0,0 +1,432 @@ +/* + * p2screen — проверка системного координатного backend Commander P2.1. + * + * Экранная модель лежит в одной EMM-странице как 80x32 пар (char, attr). + * Вывод: WINREST; проверка: RDCHAR; сдвиг: прямоугольный SCROLL 55h. + * BIOS window descriptors и прямой доступ к VRAM не используются. + */ + +#include +#include +#include +#include +#include +#include "p2_screen.h" +#include "sc_glyphs.h" + +#define LEFT_X 1u +#define RIGHT_X 41u +#define PANEL_Y 1u +#define PANEL_W 38u +#define PANEL_H 27u + +#define ATTR_UI COLOR(COLOR_BLACK, COLOR_CYAN) +#define ATTR_FRAME ATTR_UI +#define ATTR_TITLE ATTR_UI +#define ATTR_TEXT ATTR_UI +#define ATTR_DIM ATTR_UI +#define ATTR_ACTIVE ATTR_UI +#define ATTR_MARK1 ATTR_UI +#define ATTR_MARK2 ATTR_UI +#define ATTR_PASS ATTR_UI +#define ATTR_FAIL ATTR_UI +#define ATTR_KEYS ATTR_UI + +static P2Cell row_buf[P2_SCREEN_W]; +static P2Cell marker_buf[40u]; +static uint8_t fail_x; +static uint8_t fail_y; +static uint16_t fail_want; +static uint16_t fail_got; +static uint8_t w3_bad; + +static uint16_t cell_offset(uint8_t x, uint8_t y) +{ + return (uint16_t)y * P2_ROW_BYTES + (uint16_t)x * 2u; +} + +static void row_fill(uint8_t ch, uint8_t attr) +{ + uint8_t x; + for (x = 0; x < P2_SCREEN_W; x++) { + row_buf[x].ch = ch; + row_buf[x].attr = attr; + } +} + +static void cells_fill(P2Cell *cells, uint8_t count, + uint8_t ch, uint8_t attr) +{ + uint8_t i; + for (i = 0; i < count; i++) { + cells[i].ch = ch; + cells[i].attr = attr; + } +} + +static void cells_text(P2Cell *cells, uint8_t width, uint8_t x, + const char *text, uint8_t attr) +{ + while (*text && x < width) { + cells[x].ch = (uint8_t)*text++; + cells[x].attr = attr; + x++; + } +} + +static void cells_bytes(P2Cell *cells, uint8_t width, uint8_t x, + const uint8_t *bytes, uint8_t count, uint8_t attr) +{ + while (count-- && x < width) { + cells[x].ch = *bytes++; + cells[x].attr = attr; + x++; + } +} + +static void cells_dec2(P2Cell *cells, uint8_t width, uint8_t x, + uint8_t value, uint8_t attr) +{ + if (x + 1u >= width) return; + cells[x].ch = (uint8_t)('0' + (value / 10u) % 10u); + cells[x].attr = attr; + cells[x + 1u].ch = (uint8_t)('0' + value % 10u); + cells[x + 1u].attr = attr; +} + +static void cells_dec4(P2Cell *cells, uint8_t width, uint8_t x, + uint16_t value, uint8_t attr) +{ + uint16_t div = 1000u; + uint8_t i; + for (i = 0; i < 4u && x + i < width; i++) { + cells[x + i].ch = (uint8_t)('0' + (value / div) % 10u); + cells[x + i].attr = attr; + div /= 10u; + } +} + +static void row_panel_frame(uint8_t left, uint8_t y) +{ + uint8_t x; + uint8_t right = (uint8_t)(left + 39u); + + if (y == 0u || y == 28u) { + row_buf[left].ch = (y == 0u) ? SC_G_TL : SC_G_BL; + row_buf[right].ch = (y == 0u) ? SC_G_TR : SC_G_BR; + row_buf[left].attr = ATTR_FRAME; + row_buf[right].attr = ATTR_FRAME; + for (x = (uint8_t)(left + 1u); x < right; x++) { + row_buf[x].ch = SC_G_H; + row_buf[x].attr = ATTR_FRAME; + } + } else { + row_buf[left].ch = SC_G_V; + row_buf[right].ch = SC_G_V; + row_buf[left].attr = ATTR_FRAME; + row_buf[right].attr = ATTR_FRAME; + } +} + +static void build_model(uint8_t page) +{ + static const uint8_t boxes[] = { + SC_G_H, SC_G_V, SC_G_TL, SC_G_TR, SC_G_BL, SC_G_BR, + SC_G_TM, SC_G_BM, SC_G_ML, SC_G_MR, SC_G_X + }; + static const uint8_t arrows[] = { + SC_G_ARROW_UP, SC_G_ARROW_DOWN, SC_G_ARROW_LEFT, SC_G_ARROW_RIGHT + }; + static const uint8_t blocks[] = { + SC_G_SHADE_LIGHT, SC_G_SHADE_MEDIUM, SC_G_SHADE_DARK, + SC_G_BLOCK, SC_G_HALF_UP, SC_G_HALF_DOWN, + SC_G_HALF_LEFT, SC_G_HALF_RIGHT, SC_G_BULLET, + SC_G_CHECK, SC_G_SQUARE + }; + uint8_t y; + + for (y = 0; y < P2_SCREEN_H; y++) { + row_fill(' ', (y < 29u) ? ATTR_TEXT : ATTR_KEYS); + + if (y <= 28u) { + row_panel_frame(0u, y); + row_panel_frame(40u, y); + } + + if (y == 0u) { + cells_text(row_buf, P2_SCREEN_W, 11u, + " LEFT PANEL ", ATTR_TITLE); + cells_text(row_buf, P2_SCREEN_W, 50u, + " RIGHT PANEL ", ATTR_TITLE); + } else if (y >= 1u && y <= 27u) { + cells_text(row_buf, P2_SCREEN_W, 2u, "L ROW", ATTR_TEXT); + cells_dec2(row_buf, P2_SCREEN_W, 8u, y, ATTR_ACTIVE); + cells_text(row_buf, P2_SCREEN_W, 42u, "R ROW", ATTR_DIM); + cells_dec2(row_buf, P2_SCREEN_W, 48u, y, ATTR_DIM); + } + + if (y == 20u) { + cells_text(row_buf, P2_SCREEN_W, 2u, "BOX", ATTR_TITLE); + cells_bytes(row_buf, P2_SCREEN_W, 6u, boxes, + (uint8_t)sizeof(boxes), ATTR_FRAME); + } else if (y == 21u) { + cells_text(row_buf, P2_SCREEN_W, 2u, "ARROWS", ATTR_TITLE); + cells_bytes(row_buf, P2_SCREEN_W, 10u, arrows, + (uint8_t)sizeof(arrows), ATTR_FRAME); + } else if (y == 22u) { + cells_text(row_buf, P2_SCREEN_W, 2u, "BLOCKS", ATTR_TITLE); + cells_bytes(row_buf, P2_SCREEN_W, 10u, blocks, + (uint8_t)sizeof(blocks), ATTR_FRAME); + } else if (y == 28u) { + cells_text(row_buf, P2_SCREEN_W, 13u, " 27 rows ", ATTR_TITLE); + cells_text(row_buf, P2_SCREEN_W, 53u, " 27 rows ", ATTR_TITLE); + } else if (y == 29u) { + cells_text(row_buf, P2_SCREEN_W, 1u, + "P2.1 SYSTEM COORDINATE BACKEND - NO BIOS WINDOWS, NO VRAM ACCESS", + ATTR_KEYS); + } else if (y == 30u) { + cells_text(row_buf, P2_SCREEN_W, 1u, + "STAGE 1: FULL WINREST + RDCHAR VERIFY", + ATTR_KEYS); + } else if (y == 31u) { + cells_text(row_buf, P2_SCREEN_W, 1u, + "SYSTEM ATTR 0x30: NO PALETTE WRITES KEY: next stage", + ATTR_KEYS); + } + + bank_write(page, (uint16_t)y * P2_ROW_BYTES, + row_buf, P2_ROW_BYTES); + } +} + +static void build_marker(uint8_t page) +{ + cells_fill(marker_buf, 20u, ' ', ATTR_MARK1); + cells_fill(marker_buf + 20u, 20u, ' ', ATTR_MARK2); + cells_text(marker_buf, 20u, 0u, "PARTIAL RECT PASS", ATTR_MARK1); + cells_text(marker_buf + 20u, 20u, 0u, "OFFSET 0x2000 OK", ATTR_MARK2); + bank_write(page, P2_MARKER_OFF, marker_buf, sizeof(marker_buf)); +} + +static void model_apply_marker(uint8_t page, uint8_t x, uint8_t y) +{ + uint8_t saved = _io_page_w3; + P2Cell *model; + + sprinter_page_w3(page); + model = (P2Cell *)0xC000u; + memcpy(model + (uint16_t)y * P2_SCREEN_W + x, + marker_buf, 20u * sizeof(P2Cell)); + memcpy(model + (uint16_t)(y + 1u) * P2_SCREEN_W + x, + marker_buf + 20u, 20u * sizeof(P2Cell)); + sprinter_page_w3(saved); +} + +static void model_status(uint8_t page, const char *text, uint8_t ok) +{ + bank_read(page, 30u * P2_ROW_BYTES, row_buf, P2_ROW_BYTES); + cells_fill(row_buf, P2_SCREEN_W, ' ', ATTR_KEYS); + cells_text(row_buf, P2_SCREEN_W, 1u, text, ATTR_KEYS); + cells_text(row_buf, P2_SCREEN_W, 62u, + ok ? "PASS" : "FAIL", ok ? ATTR_PASS : ATTR_FAIL); + if (!ok) { + cells_text(row_buf, P2_SCREEN_W, 68u, "AT", ATTR_FAIL); + cells_dec2(row_buf, P2_SCREEN_W, 71u, fail_x, ATTR_FAIL); + row_buf[73u].ch = ','; + row_buf[73u].attr = ATTR_FAIL; + cells_dec2(row_buf, P2_SCREEN_W, 74u, fail_y, ATTR_FAIL); + } + bank_write(page, 30u * P2_ROW_BYTES, row_buf, P2_ROW_BYTES); +} + +static void present_rect(uint8_t page, uint8_t x, uint8_t y, + uint8_t width, uint8_t height, uint16_t offset) +{ + uint8_t saved = _io_page_w3; + p2_winrest(y, x, height, width, page, offset); + if (_io_page_w3 != saved) w3_bad = 1u; +} + +static void present_model_row(uint8_t page, uint8_t x, + uint8_t y, uint8_t width) +{ + present_rect(page, x, y, width, 1u, cell_offset(x, y)); +} + +static uint8_t verify_model(uint8_t page) +{ + uint8_t saved = _io_page_w3; + uint8_t x; + uint8_t y; + + for (y = 0; y < P2_SCREEN_H; y++) { + bank_read(page, (uint16_t)y * P2_ROW_BYTES, + row_buf, P2_ROW_BYTES); + for (x = 0; x < P2_SCREEN_W; x++) { + uint16_t got = rdchar(x, y); + uint16_t want = ((uint16_t)row_buf[x].attr << 8) | + row_buf[x].ch; + if (got != want) { + fail_x = x; + fail_y = y; + fail_want = want; + fail_got = got; + return 0u; + } + } + } + + if (_io_page_w3 != saved) w3_bad = 1u; + return w3_bad ? 0u : 1u; +} + +static uint8_t report_verify(uint8_t page, const char *stage) +{ + uint8_t ok = verify_model(page); + model_status(page, stage, ok); + present_model_row(page, 0u, 30u, P2_SCREEN_W); + return ok; +} + +static void mapped_new_row(P2Cell *line, uint8_t width, + uint8_t direction, uint16_t sequence) +{ + cells_fill(line, width, ' ', ATTR_ACTIVE); + cells_text(line, width, 1u, + (direction == 1u) ? "NEW BOTTOM ROW" : "NEW TOP ROW", + ATTR_ACTIVE); + cells_dec4(line, width, 17u, sequence, ATTR_ACTIVE); +} + +static void model_scroll(uint8_t page, uint8_t x, uint8_t y, + uint8_t width, uint8_t height, + uint8_t direction, uint16_t sequence) +{ + uint8_t saved = _io_page_w3; + P2Cell *model; + uint8_t i; + + sprinter_page_w3(page); + model = (P2Cell *)0xC000u; + + if (direction == 1u) { + for (i = 0; i + 1u < height; i++) { + memmove(model + (uint16_t)(y + i) * P2_SCREEN_W + x, + model + (uint16_t)(y + i + 1u) * P2_SCREEN_W + x, + (uint16_t)width * sizeof(P2Cell)); + } + mapped_new_row(model + (uint16_t)(y + height - 1u) * P2_SCREEN_W + x, + width, direction, sequence); + } else { + i = (uint8_t)(height - 1u); + while (i != 0u) { + memmove(model + (uint16_t)(y + i) * P2_SCREEN_W + x, + model + (uint16_t)(y + i - 1u) * P2_SCREEN_W + x, + (uint16_t)width * sizeof(P2Cell)); + i--; + } + mapped_new_row(model + (uint16_t)y * P2_SCREEN_W + x, + width, direction, sequence); + } + + sprinter_page_w3(saved); +} + +static void system_scroll(uint8_t page, uint8_t x, uint8_t y, + uint8_t width, uint8_t height, + uint8_t direction, uint16_t sequence) +{ + uint8_t saved; + uint8_t open_y; + + model_scroll(page, x, y, width, height, direction, sequence); + saved = _io_page_w3; + scroll(x, y, width, height, direction, 0u); + if (_io_page_w3 != saved) w3_bad = 1u; + + open_y = (direction == 1u) ? (uint8_t)(y + height - 1u) : y; + present_model_row(page, x, open_y, width); +} + +static uint8_t wait_stage(void) +{ + uint16_t key = getkey(); + return ((uint8_t)key == 0x1Bu) ? 0u : 1u; +} + +int main(void) +{ + uint8_t previous_mode = gettextmode(); + uint8_t block; + uint8_t page; + uint8_t ok = 1u; + uint16_t i; + + (void)fail_want; + (void)fail_got; + + if (settextmode(TEXT_MODE_80x32) != 0) return 1; + + block = mem_alloc_pages(1u); + if (block == 0u) { + if (previous_mode == TEXT_MODE_40x32 || + previous_mode == TEXT_MODE_80x32) { + (void)settextmode(previous_mode); + } + return 2; + } + page = mem_get_page(block, 0u); + if (page == 0u) { + mem_free_block(block); + if (previous_mode == TEXT_MODE_40x32 || + previous_mode == TEXT_MODE_80x32) { + (void)settextmode(previous_mode); + } + return 3; + } + build_model(page); + build_marker(page); + + present_rect(page, 0u, 0u, P2_SCREEN_W, P2_SCREEN_H, 0u); + if (!report_verify(page, "STAGE 1: FULL WINREST + RDCHAR")) ok = 0u; + if (!wait_stage()) goto done; + + present_rect(page, 30u, 14u, 20u, 2u, P2_MARKER_OFF); + model_apply_marker(page, 30u, 14u); + if (!report_verify(page, "STAGE 2: PARTIAL OFFSET 0x2000")) ok = 0u; + if (!wait_stage()) goto done; + + build_model(page); + present_rect(page, 0u, 0u, P2_SCREEN_W, P2_SCREEN_H, 0u); + system_scroll(page, LEFT_X, PANEL_Y, PANEL_W, PANEL_H, 1u, 1u); + if (!report_verify(page, "STAGE 3: LEFT RECT SCROLL UP")) ok = 0u; + if (!wait_stage()) goto done; + + system_scroll(page, RIGHT_X, PANEL_Y, PANEL_W, PANEL_H, 2u, 2u); + if (!report_verify(page, "STAGE 4: RIGHT RECT SCROLL DOWN")) ok = 0u; + if (!wait_stage()) goto done; + + model_status(page, "STAGE 5: 1000 RECT SCROLL OPERATIONS", 1u); + present_model_row(page, 0u, 30u, P2_SCREEN_W); + for (i = 0u; i < 1000u; i++) { + uint8_t direction = (i & 1u) ? 2u : 1u; + system_scroll(page, LEFT_X, PANEL_Y, PANEL_W, PANEL_H, + direction, (uint16_t)(i + 1u)); + } + if (!report_verify(page, "FINAL: MODEL/RDCHAR/W3 AFTER 1000")) ok = 0u; + if (!ok) { + model_status(page, "FINAL P2.1 RESULT", 0u); + present_model_row(page, 0u, 30u, P2_SCREEN_W); + } + (void)wait_stage(); + +done: + mem_free_block(block); + textattr(COLOR(COLOR_WHITE, COLOR_BLACK)); + clrscr(); + if (previous_mode == TEXT_MODE_40x32 || + previous_mode == TEXT_MODE_80x32) { + (void)settextmode(previous_mode); + } + return ok ? 0 : 1; +} diff --git a/applications/Volkov/tests/p2_screen/sc_glyphs.h b/applications/Volkov/tests/p2_screen/sc_glyphs.h new file mode 100644 index 0000000..0d293ea --- /dev/null +++ b/applications/Volkov/tests/p2_screen/sc_glyphs.h @@ -0,0 +1,41 @@ +/* + * sc_glyphs.h — raw-коды CP866/CP437 для интерфейса Commander. + * + * Байты ниже являются экранными глифами, включая управляющий диапазон + * 0x18..0x1B. Их следует класть в ScCell или передавать WRCHAR/WINREST, + * но не печатать функцией, интерпретирующей CR/LF/BS/TAB. + */ + +#ifndef SC_GLYPHS_H +#define SC_GLYPHS_H + +#define SC_G_H 0xC4u +#define SC_G_V 0xB3u +#define SC_G_TL 0xDAu +#define SC_G_TR 0xBFu +#define SC_G_BL 0xC0u +#define SC_G_BR 0xD9u +#define SC_G_TM 0xC2u +#define SC_G_BM 0xC1u +#define SC_G_ML 0xC3u +#define SC_G_MR 0xB4u +#define SC_G_X 0xC5u + +#define SC_G_ARROW_UP 0x18u +#define SC_G_ARROW_DOWN 0x19u +#define SC_G_ARROW_RIGHT 0x1Au +#define SC_G_ARROW_LEFT 0x1Bu + +#define SC_G_SHADE_LIGHT 0xB0u +#define SC_G_SHADE_MEDIUM 0xB1u +#define SC_G_SHADE_DARK 0xB2u +#define SC_G_BLOCK 0xDBu +#define SC_G_HALF_UP 0xDFu +#define SC_G_HALF_DOWN 0xDCu +#define SC_G_HALF_LEFT 0xDDu +#define SC_G_HALF_RIGHT 0xDEu +#define SC_G_BULLET 0xF9u +#define SC_G_CHECK 0xFBu +#define SC_G_SQUARE 0xFEu + +#endif diff --git a/applications/Volkov/tests/p3_data/INNER.TXT b/applications/Volkov/tests/p3_data/INNER.TXT new file mode 100644 index 0000000..b897cf7 --- /dev/null +++ b/applications/Volkov/tests/p3_data/INNER.TXT @@ -0,0 +1 @@ +File inside TESTDIR used to verify independent panel paths and synthetic parent. diff --git a/applications/Volkov/tests/p3_data/P3ROOT.TXT b/applications/Volkov/tests/p3_data/P3ROOT.TXT new file mode 100644 index 0000000..98d0bb5 --- /dev/null +++ b/applications/Volkov/tests/p3_data/P3ROOT.TXT @@ -0,0 +1 @@ +Sprinter Commander P3 root fixture. diff --git a/applications/Volkov/tests/p3_data/README.md b/applications/Volkov/tests/p3_data/README.md new file mode 100644 index 0000000..9a5776c --- /dev/null +++ b/applications/Volkov/tests/p3_data/README.md @@ -0,0 +1,12 @@ +# Данные MAME-сценария P3 + +После `make floppy` сценарий создаёт на тестовой FAT12-дискете каталог +`TESTDIR`, кладёт в него `INNER.TXT`, а в корень — `P3ROOT.TXT` и `ZETA.BIN`. + +Ожидаемая сортировка корня: каталог `TESTDIR`, затем файлы по имени. Внутри +`TESTDIR` Commander сам добавляет синтетическую запись `..` перед +`INNER.TXT`; DSS-записи `.`/`..` в EMM не копируются. + +Fixture также является регрессией wildcard: первый P3-прогон с `ffirst("*")` +увидел только `TESTDIR`. Принятый источник обязан использовать `*.*`, иначе +DSS не возвращает имена с расширением. diff --git a/applications/Volkov/tests/p3_data/ZETA.BIN b/applications/Volkov/tests/p3_data/ZETA.BIN new file mode 100644 index 0000000..94ea3ed --- /dev/null +++ b/applications/Volkov/tests/p3_data/ZETA.BIN @@ -0,0 +1 @@ +Second root file used to verify directory-first and alphabetical sorting. diff --git a/applications/Volkov/tests/p3_dir_probe/Makefile b/applications/Volkov/tests/p3_dir_probe/Makefile new file mode 100644 index 0000000..8933b44 --- /dev/null +++ b/applications/Volkov/tests/p3_dir_probe/Makefile @@ -0,0 +1,11 @@ +# Диагностическая проба предельного каталога P3. + +PROJ_ROOT := $(abspath $(CURDIR)/../../../..) +EXAMPLE := p3dir +MEMORY := small +ALLOCS ?= 3000 +EXTRA_FLAGS := --safe --max-allocs $(ALLOCS) + +include $(PROJ_ROOT)/app.mk + +$(EXE): Makefile diff --git a/applications/Volkov/tests/p3_dir_probe/p3dir.c b/applications/Volkov/tests/p3_dir_probe/p3dir.c new file mode 100644 index 0000000..1a0898f --- /dev/null +++ b/applications/Volkov/tests/p3_dir_probe/p3dir.c @@ -0,0 +1,93 @@ +/* + * p3dir.c — изолированная диагностика большого FAT-каталога DSS. + * + * Проба ничего не изменяет: входит в BIGDIR, полностью исчерпывает + * F_FIRST/F_NEXT, печатает фактический предел и проверяет возврат в корень. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define FIND_ATTRS (FA_RDONLY | FA_HIDDEN | FA_SYSTEM | FA_DIREC | FA_ARCH) + +static ffblk_t find_buf; +static char cwd[256]; +static char last_name[13]; +static char name_511[13]; +static char name_512[13]; +static char name_513[13]; +static uint16_t enum_count; +static int enum_errno; + +static void save_name(char *dst) +{ + strncpy(dst, find_buf.found_name, 12u); + dst[12] = '\0'; +} + +int main(void) +{ + int rc; + + (void)settextmode(TEXT_MODE_80x32); + clrscr_attr(0x17); + gotoxy(0, 0); + textattr(0x1Fu); + cputs("P3 BIGDIR diagnostic\r\n\r\n"); + textattr(0x17u); + + errno = 0; + rc = chdir("\\BIGDIR"); + printf("1 chdir BIGDIR: rc=%d errno=%d\n", rc, errno); + if (rc != 0) goto done; + + errno = 0; + if (getcwd(cwd, (uint16_t)sizeof(cwd))) + printf("2 cwd: %s\n", cwd); + else + printf("2 getcwd failed: errno=%d\n", errno); + + errno = 0; + rc = ffirst("*.*", &find_buf, FIND_ATTRS); + printf("3 ffirst: rc=%d errno=%d\n", rc, errno); + enum_count = 0u; + if (rc == 0) { + for (;;) { + enum_count++; + save_name(last_name); + if (enum_count == 511u) save_name(name_511); + if (enum_count == 512u) save_name(name_512); + if (enum_count == 513u) save_name(name_513); + errno = 0; + if (fnext(&find_buf) != 0) break; + if (enum_count == 2000u) { + puts("Loop guard reached."); + break; + } + } + } + enum_errno = errno; + printf("4 enum: count=%u errno=%d last=%s\n", + (unsigned)enum_count, enum_errno, last_name); + printf("5 boundary: 511=%s 512=%s 513=%s\n", + name_511, name_512, name_513); + + errno = 0; + rc = chdir("\\"); + printf("6 chdir root: rc=%d errno=%d\n", rc, errno); + errno = 0; + if (getcwd(cwd, (uint16_t)sizeof(cwd))) + printf("7 cwd: %s\n", cwd); + else + printf("7 getcwd failed: errno=%d\n", errno); + +done: + textattr(0x1Eu); + cputs("\r\nProbe stopped; MAME test will take a snapshot."); + for (;;) (void)getkey(); +} diff --git a/applications/Volkov/tests/p3_enum_matrix/Makefile b/applications/Volkov/tests/p3_enum_matrix/Makefile new file mode 100644 index 0000000..6101edf --- /dev/null +++ b/applications/Volkov/tests/p3_enum_matrix/Makefile @@ -0,0 +1,11 @@ +# Матрица влияния W3/EMM на длинный F_FIRST/F_NEXT. + +PROJ_ROOT := $(abspath $(CURDIR)/../../../..) +EXAMPLE := p3map +MEMORY := big +ALLOCS ?= 3000 +EXTRA_FLAGS := --safe --max-allocs $(ALLOCS) --bank 1=bank1_enum.c + +include $(PROJ_ROOT)/app.mk + +$(EXE): Makefile bank1_enum.c diff --git a/applications/Volkov/tests/p3_enum_matrix/bank1_enum.c b/applications/Volkov/tests/p3_enum_matrix/bank1_enum.c new file mode 100644 index 0000000..df14333 --- /dev/null +++ b/applications/Volkov/tests/p3_enum_matrix/bank1_enum.c @@ -0,0 +1,90 @@ +/* + * bank1_enum.c — отделяет влияние адреса ffblk от переключений W3. + * + * Весь перебор исполняется при подключённом BANK1, а диагностический DTA + * занимает последние 256 байт W1. Это историческая матрица, доказавшая, + * что ни адрес DTA, ни чтение/запись W3 не снимают предел DSS. mode=0 не + * трогает W3, mode=1 читает байт EMM, mode=2 пишет 24 байта EMM. + */ + +#include +#include +#include +#include + +#define FIND_ATTRS (FA_RDONLY | FA_HIDDEN | FA_SYSTEM | FA_DIREC | FA_ARCH) + +__at (0x7F00) static ffblk_t find_buf; + +/* Копии wrapper находятся в том же W1, что и цикл. Так проверяется не + * только адрес ffblk, но и адрес самой инструкции RST/возврата из DSS. */ +static int p3_ffirst_local(const char *pattern, ffblk_t *buf, + uint8_t attrib) __naked +{ + (void)pattern; (void)buf; (void)attrib; + __asm + ld iy, #2 + add iy, sp + ld a, 0 (iy) + push ix + ld bc, #0x0119 + rst #0x10 + pop ix + pop hl + inc sp + jr c, p3_ffirst_fail$ + ld de, #0 + jp (hl) + p3_ffirst_fail$: + call __errno_set + ld de, #-1 + jp (hl) + __endasm; +} + +static int p3_fnext_local(ffblk_t *buf) __naked +{ + (void)buf; + __asm + push ix + ex de, hl + ld c, #0x1A + rst #0x10 + pop ix + jr c, p3_fnext_fail$ + ld de, #0 + ret + p3_fnext_fail$: + call __errno_set + ld de, #-1 + ret + __endasm; +} + +uint16_t p3_enum_mode(uint8_t mode, uint8_t page, int *end_errno) __banked +{ + uint16_t count = 0u; + uint8_t payload[24]; + uint16_t offset; + + errno = 0; + if ((mode >= 3u ? + p3_ffirst_local("*.*", &find_buf, FIND_ATTRS) : + ffirst("*.*", &find_buf, FIND_ATTRS)) == 0) { + for (;;) { + count++; + if (mode == 1u) { + payload[0] = bank_load_byte(page, 0u); + } else if (mode == 2u || mode == 4u) { + offset = (uint16_t)(((count - 1u) % 640u) * 24u); + bank_write(page, offset, payload, 24u); + } + errno = 0; + if ((mode >= 3u ? p3_fnext_local(&find_buf) : + fnext(&find_buf)) != 0) break; + if (count == 2000u) break; + } + } + *end_errno = errno; + return count; +} diff --git a/applications/Volkov/tests/p3_enum_matrix/p3map.c b/applications/Volkov/tests/p3_enum_matrix/p3map.c new file mode 100644 index 0000000..dd18eb7 --- /dev/null +++ b/applications/Volkov/tests/p3_enum_matrix/p3map.c @@ -0,0 +1,56 @@ +/* p3map.c — экранный отчёт матрицы длинного перебора каталога. */ + +#include +#include +#include +#include +#include +#include + +uint16_t p3_enum_mode(uint8_t mode, uint8_t page, int *end_errno) __banked; + +int main(void) +{ + uint8_t block; + uint8_t page; + uint8_t mode; + uint16_t count; + int end_errno; + + (void)settextmode(TEXT_MODE_80x32); + clrscr_attr(0x17u); + gotoxy(0u, 0u); + textattr(0x1Fu); + cputs("P3 F_NEXT / W3 matrix\r\n\r\n"); + textattr(0x17u); + + block = mem_alloc_pages(1u); + page = block ? mem_get_page(block, 0u) : 0u; + printf("EMM: block=%u page=%02X errno=%d\n", + (unsigned)block, (unsigned)page, errno); + if (!block || !page) goto done; + + for (mode = 0u; mode != 5u; mode++) { + errno = 0; + if (chdir("\\BIGDIR") != 0) { + printf("mode %u: chdir error=%d\n", (unsigned)mode, errno); + break; + } + count = p3_enum_mode(mode, page, &end_errno); + printf("mode %u: count=%u end_errno=%d\n", + (unsigned)mode, (unsigned)count, end_errno); + errno = 0; + if (chdir("\\") != 0) { + printf("root after mode %u: error=%d\n", + (unsigned)mode, errno); + break; + } + } + + mem_free_block(block); + +done: + textattr(0x1Eu); + cputs("\r\nProbe stopped; MAME test will take a snapshot."); + for (;;) (void)getkey(); +} diff --git a/applications/Volkov/tests/p3_scan_probe/Makefile b/applications/Volkov/tests/p3_scan_probe/Makefile new file mode 100644 index 0000000..1568203 --- /dev/null +++ b/applications/Volkov/tests/p3_scan_probe/Makefile @@ -0,0 +1,14 @@ +# Полный scan/store/sort большого каталога теми же модулями, что Commander. + +PROJ_ROOT := $(abspath $(CURDIR)/../../../..) +EXAMPLE := p3scan +MEMORY := big +ALLOCS ?= 3000 +EXTRA_FLAGS := --safe --max-allocs $(ALLOCS) -I ../../include \ + --bank 1=../../src/sc_source_fs.c \ + --bank 1=../../src/sc_sort.c +EXTRA_SRCS := ../../src/sc_panel.c ../../src/sc_store.c + +include $(PROJ_ROOT)/app.mk + +$(EXE): Makefile ../../src/sc_source_fs.c ../../src/sc_sort.c diff --git a/applications/Volkov/tests/p3_scan_probe/p3scan.c b/applications/Volkov/tests/p3_scan_probe/p3scan.c new file mode 100644 index 0000000..64b579e --- /dev/null +++ b/applications/Volkov/tests/p3_scan_probe/p3scan.c @@ -0,0 +1,70 @@ +/* + * p3scan.c — диагностика полного пути scan/store/sort большого каталога. + * + * Использует те же банковые модули P3, но не делает panel change/rollback, + * поэтому сохраняет точные rc, errno и состояние панели в точке отказа. + */ + +#include +#include +#include +#include +#include +#include +#include "sc_panel.h" +#include "sc_source.h" +#include "sc_store.h" + +static ScPanel panel; +static ScDirEntry entry; + +int main(void) +{ + uint8_t block; + uint8_t page; + int rc; + int saved_errno; + + (void)settextmode(TEXT_MODE_80x32); + clrscr_attr(0x17); + gotoxy(0, 0); + textattr(0x1Fu); + cputs("P3 scan/store/sort diagnostic\r\n\r\n"); + textattr(0x17u); + + block = mem_alloc_pages(1u); + page = block ? mem_get_page(block, 0u) : 0u; + printf("EMM: block=%u page=%02X errno=%d\n", + (unsigned)block, (unsigned)page, errno); + if (!block || !page) goto done; + + memset(&panel, 0, sizeof(panel)); + strcpy(panel.path, "\\BIGDIR"); + panel.phys_page = page; + + puts("Calling sc_source_fs_scan..."); + errno = 0; + rc = sc_source_fs_scan(&panel); + saved_errno = errno; + printf("scan: rc=%d errno=%d count=%u trunc=%u\n", + rc, saved_errno, (unsigned)panel.count, + (unsigned)panel.truncated); + printf("view: cursor=%u top=%u total=%lu\n", + (unsigned)panel.cursor, (unsigned)panel.top, + (unsigned long)panel.total_size); + + if (panel.count != 0u && sc_store_get(&panel, 0u, &entry) == 0) + printf("first: %s attr=%02X flags=%02X\n", entry.name, + (unsigned)entry.attr, (unsigned)entry.flags); + if (panel.count != 0u && + sc_store_get(&panel, (uint16_t)(panel.count - 1u), &entry) == 0) + printf("last : %s attr=%02X flags=%02X\n", entry.name, + (unsigned)entry.attr, (unsigned)entry.flags); + + mem_free_block(block); + +done: + textattr(0x1Eu); + cputs("\r\nProbe stopped; MAME test will take snapshots."); + for (;;) (void)getkey(); +} diff --git a/applications/Volkov/tests/p3_store_probe/Makefile b/applications/Volkov/tests/p3_store_probe/Makefile new file mode 100644 index 0000000..ede2a05 --- /dev/null +++ b/applications/Volkov/tests/p3_store_probe/Makefile @@ -0,0 +1,13 @@ +# Синтетическая проверка границы одной EMM-страницы панели: 640/641. + +PROJ_ROOT := $(abspath $(CURDIR)/../../../..) +EXAMPLE := p3store +MEMORY := big +ALLOCS ?= 3000 +EXTRA_FLAGS := --safe --max-allocs $(ALLOCS) -I ../../include +EXTRA_SRCS := ../../src/sc_store.c + +include $(PROJ_ROOT)/app.mk + +$(EXE): Makefile ../../src/sc_store.c ../../include/sc_entry.h \ + ../../include/sc_panel.h ../../include/sc_store.h diff --git a/applications/Volkov/tests/p3_store_probe/p3store.c b/applications/Volkov/tests/p3_store_probe/p3store.c new file mode 100644 index 0000000..906b526 --- /dev/null +++ b/applications/Volkov/tests/p3_store_probe/p3store.c @@ -0,0 +1,128 @@ +/* + * p3store.c — синтетическая проверка предела EMM-store панели. + * + * DSS не участвует: проба обязана дойти до продуктовой границы 640/641, + * которая недостижима через каталог стабильного DSS 1.71 с пределом 512. + */ + +#include +#include +#include +#include +#include +#include "sc_panel.h" +#include "sc_store.h" + +#define STORE_BYTES ((uint16_t)(SC_DIR_CAPACITY * sizeof(ScDirEntry))) + +static ScPanel panel; +static ScDirEntry entry; +static ScDirEntry check; + +static uint32_t expected_size(uint16_t index) +{ + return (uint32_t)index * 257ul + 3ul; +} + +static uint8_t verify_entry(uint16_t index) +{ + if (sc_store_get(&panel, index, &check) != 0) return 0u; + return check.name[0] == 'R' && check.name[1] == (char)(index & 0x7Fu) && + check.size == expected_size(index) && check.date == index && + check.time == (uint16_t)(index ^ 0x55AAu) && + check.reserved == 0xA5u; +} + +int main(void) +{ + uint16_t total_before; + uint16_t free_before; + uint16_t total_after; + uint16_t free_after; + uint16_t written = 0u; + uint16_t i; + uint8_t block; + uint8_t page; + uint8_t first_ok; + uint8_t middle_ok; + uint8_t last_ok; + uint8_t put_641_blocked; + uint8_t get_641_blocked; + uint8_t tail_ok; + uint8_t emm_restored; + + (void)settextmode(TEXT_MODE_80x32); + clrscr_attr(0x17u); + gotoxy(0u, 0u); + textattr(0x1Fu); + cputs("P3 EMM store 640/641 diagnostic\r\n\r\n"); + textattr(0x17u); + + printf("entry=%u capacity=%u bytes=%u reserve=%u\n", + (unsigned)sizeof(ScDirEntry), (unsigned)SC_DIR_CAPACITY, + (unsigned)STORE_BYTES, (unsigned)(0x4000u - STORE_BYTES)); + + mem_info(&total_before, &free_before); + block = mem_alloc_pages(1u); + page = block ? mem_get_page(block, 0u) : 0u; + printf("EMM: block=%u page=%02X free_before=%u\n", + (unsigned)block, (unsigned)page, (unsigned)free_before); + if (!block || !page) goto failed_alloc; + + memset(&panel, 0, sizeof(panel)); + panel.phys_page = page; + bank_store_byte(page, STORE_BYTES, 0x5Au); + bank_store_byte(page, 0x3FFFu, 0xC3u); + + for (i = 0u; i < SC_DIR_CAPACITY; i++) { + memset(&entry, 0, sizeof(entry)); + entry.name[0] = 'R'; + entry.name[1] = (char)(i & 0x7Fu); + entry.size = expected_size(i); + entry.date = i; + entry.time = (uint16_t)(i ^ 0x55AAu); + entry.reserved = 0xA5u; + if (sc_store_put(&panel, i, &entry) != 0) break; + written++; + } + panel.count = written; + + first_ok = verify_entry(0u); + middle_ok = verify_entry(319u); + last_ok = verify_entry(639u); + put_641_blocked = (sc_store_put(&panel, 640u, &entry) != 0); + get_641_blocked = (sc_store_get(&panel, 640u, &check) != 0); + tail_ok = bank_load_byte(page, STORE_BYTES) == 0x5Au && + bank_load_byte(page, 0x3FFFu) == 0xC3u; + + printf("write: count=%u first=%u middle=%u last=%u\n", + (unsigned)written, (unsigned)first_ok, + (unsigned)middle_ok, (unsigned)last_ok); + printf("guard: put640=%u get640=%u tail=%u\n", + (unsigned)put_641_blocked, (unsigned)get_641_blocked, + (unsigned)tail_ok); + + mem_free_block(block); + mem_info(&total_after, &free_after); + emm_restored = total_before == total_after && free_before == free_after; + printf("cleanup: free_after=%u restored=%u\n", + (unsigned)free_after, (unsigned)emm_restored); + + textattr((written == 640u && first_ok && middle_ok && last_ok && + put_641_blocked && get_641_blocked && tail_ok && emm_restored) ? + 0x1Eu : 0x4Fu); + cputs((written == 640u && first_ok && middle_ok && last_ok && + put_641_blocked && get_641_blocked && tail_ok && emm_restored) ? + "\r\nPASS: store boundary is intact." : + "\r\nFAIL: store boundary violation."); + goto stopped; + +failed_alloc: + textattr(0x4Fu); + cputs("\r\nFAIL: cannot allocate EMM page."); + +stopped: + textattr(0x1Eu); + cputs("\r\nProbe stopped; MAME test will take a snapshot."); + for (;;) (void)getkey(); +} diff --git a/applications/Volkov/tests/p4_bank_io_probe/Makefile b/applications/Volkov/tests/p4_bank_io_probe/Makefile new file mode 100644 index 0000000..3d76d9a --- /dev/null +++ b/applications/Volkov/tests/p4_bank_io_probe/Makefile @@ -0,0 +1,11 @@ +# P4 — прямой файловый обмен через EMM-страницу в W3. + +PROJ_ROOT := $(abspath $(CURDIR)/../../../..) +EXAMPLE := p4bio +MEMORY := big +ALLOCS ?= 3000 +EXTRA_FLAGS := --safe --max-allocs $(ALLOCS) + +include $(PROJ_ROOT)/app.mk + +$(EXE): Makefile diff --git a/applications/Volkov/tests/p4_bank_io_probe/p4bio.c b/applications/Volkov/tests/p4_bank_io_probe/p4bio.c new file mode 100644 index 0000000..1938591 --- /dev/null +++ b/applications/Volkov/tests/p4_bank_io_probe/p4bio.c @@ -0,0 +1,136 @@ +/* + * p4bio.c — целевая проба bank_read_page()/bank_write_page(). + * + * Проверяет перенос 4096 байт через одну EMM-страницу без полноразмерного + * near-буфера, восстановление W3, контроль границ и отсутствие утечки EMM. + * Временный файл создаётся только на тестовой дискете A: и удаляется. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define TEST_PATH "A:\\P4BIO.TMP" +#define TEST_SIZE 4096u +#define READ_OFFSET 4096u + +static uint8_t chunk[256]; + +static uint8_t pattern(uint16_t pos) +{ + return (uint8_t)((uint8_t)(pos * 37u) + (uint8_t)(pos >> 8) + 0x5Au); +} + +static void prepare_page(uint8_t page) +{ + uint16_t base; + uint16_t i; + + for (base = 0u; base < TEST_SIZE; base += (uint16_t)sizeof(chunk)) { + for (i = 0u; i < (uint16_t)sizeof(chunk); i++) { + chunk[i] = pattern((uint16_t)(base + i)); + } + bank_write(page, base, chunk, (uint16_t)sizeof(chunk)); + } +} + +static uint8_t verify_page(uint8_t page) +{ + uint16_t i; + + for (i = 0u; i < TEST_SIZE; i++) { + if (bank_load_byte(page, (uint16_t)(READ_OFFSET + i)) != pattern(i)) { + return 0u; + } + } + return 1u; +} + +int main(void) +{ + uint16_t total_before; + uint16_t free_before; + uint16_t total_after; + uint16_t free_after; + uint8_t block = 0u; + uint8_t page = 0u; + uint8_t w3_before; + uint8_t write_w3_ok = 0u; + uint8_t read_w3_ok = 0u; + uint8_t data_ok = 0u; + uint8_t range_ok = 0u; + uint8_t cleanup_ok; + int fd = -1; + int written = -1; + int loaded = -1; + + (void)settextmode(TEXT_MODE_80x32); + clrscr_attr(0x17u); + gotoxy(0u, 0u); + textattr(0x1Fu); + cputs("P4 bank page file I/O diagnostic\r\n\r\n"); + textattr(0x17u); + + (void)unlink(TEST_PATH); + mem_info(&total_before, &free_before); + block = mem_alloc_pages(1u); + page = block ? mem_get_page(block, 0u) : 0u; + if (!block || !page) goto cleanup; + + prepare_page(page); + fd = open(TEST_PATH, O_WRONLY | O_CREAT | O_EXCL); + if (fd < 0) goto cleanup; + w3_before = _io_page_w3; + written = bank_write_page(fd, page, 0u, TEST_SIZE); + write_w3_ok = (_io_page_w3 == w3_before); + if (close(fd) != 0) goto cleanup; + fd = -1; + if (written != TEST_SIZE) goto cleanup; + + fd = open(TEST_PATH, O_RDONLY); + if (fd < 0) goto cleanup; + w3_before = _io_page_w3; + loaded = bank_read_page(fd, page, READ_OFFSET, TEST_SIZE); + read_w3_ok = (_io_page_w3 == w3_before); + if (close(fd) != 0) goto cleanup; + fd = -1; + if (loaded != TEST_SIZE) goto cleanup; + + data_ok = verify_page(page); + errno = 0; + w3_before = _io_page_w3; + range_ok = bank_write_page(-1, page, 0x3FFFu, 2u) == -1 && + errno == EINVAL && _io_page_w3 == w3_before; + +cleanup: + if (fd >= 0) (void)close(fd); + (void)unlink(TEST_PATH); + if (block != 0u) mem_free_block(block); + mem_info(&total_after, &free_after); + cleanup_ok = total_before == total_after && free_before == free_after; + + printf("page=%02X write=%d read=%d data=%u\n", + (unsigned)page, written, loaded, (unsigned)data_ok); + printf("W3: write=%u read=%u range=%u errno=%d\n", + (unsigned)write_w3_ok, (unsigned)read_w3_ok, + (unsigned)range_ok, errno); + printf("EMM: before=%u after=%u restored=%u\n", + (unsigned)free_before, (unsigned)free_after, + (unsigned)cleanup_ok); + + textattr((written == TEST_SIZE && loaded == TEST_SIZE && data_ok && + write_w3_ok && read_w3_ok && range_ok && cleanup_ok) ? + 0x1Eu : 0x4Fu); + cputs((written == TEST_SIZE && loaded == TEST_SIZE && data_ok && + write_w3_ok && read_w3_ok && range_ok && cleanup_ok) ? + "\r\nPASS: bank page file I/O is intact." : + "\r\nFAIL: bank page file I/O violation."); + textattr(0x1Eu); + cputs("\r\nProbe stopped; MAME test will take a snapshot."); + for (;;) (void)getkey(); +} diff --git a/applications/Volkov/tests/p4_exec_child/Makefile b/applications/Volkov/tests/p4_exec_child/Makefile new file mode 100644 index 0000000..dd03f2b --- /dev/null +++ b/applications/Volkov/tests/p4_exec_child/Makefile @@ -0,0 +1,9 @@ +# Дочерняя программа для интеграционного EXEC-теста Commander. + +PROJ_ROOT := $(abspath $(CURDIR)/../../../..) +EXAMPLE := p4child +MEMORY := tiny +ALLOCS ?= 3000 +EXTRA_FLAGS := --safe --max-allocs $(ALLOCS) + +include $(PROJ_ROOT)/app.mk diff --git a/applications/Volkov/tests/p4_exec_child/p4child.c b/applications/Volkov/tests/p4_exec_child/p4child.c new file mode 100644 index 0000000..89294d3 --- /dev/null +++ b/applications/Volkov/tests/p4_exec_child/p4child.c @@ -0,0 +1,80 @@ +/* + * p4child.c — дочерняя программа, намеренно меняющая состояние системы. + * + * Создаёт по одному файлу в каталогах обеих панелей, оставляет CWD в + * EXECLEFT, переключает экран в 40x32 и портит используемые Commander + * записи палитры. Родитель обязан восстановить всё это после EXEC. + */ + +#include +#include +#include +#include + +static char child_cwd[256]; + +static int write_marker(const char *path, const char *text, uint16_t size) +{ + int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC); + int result = 0; + + if (fd < 0) return -1; + if (write(fd, text, size) != (int)size) result = -1; + if (close(fd) != 0) result = -1; + return result; +} + +static void damage_commander_palette(void) +{ + static const uint8_t attrs[] = { + 0x1Fu, 0x1Eu, 0x1Bu, 0x30u, 0x70u, 0x4Fu, 0x1Cu, 0x3Cu + }; + uint8_t i; + uint8_t plane; + + for (i = 0u; i < (uint8_t)sizeof(attrs); i++) { + for (plane = 0u; plane < 4u; plane++) { + text_pal_set_color(plane, attrs[i], 0xFFu, 0x00u, 0xFFu); + } + } +} + +int main(void) +{ + static const char left_text[] = "created by P4CHILD\n"; + static const char right_text[] = "passive refresh by P4CHILD\n"; + int left_result; + int right_result; + + left_result = chdir("\\EXECLEFT"); + if (left_result == 0) { + left_result = write_marker("CREATED.TXT", left_text, + (uint16_t)(sizeof(left_text) - 1u)); + } + /* Идём через корень, оставляя проверяемые каталоги в формате 8.3. */ + right_result = chdir(".."); + if (right_result == 0) right_result = chdir("EXECRGHT"); + if (right_result == 0) { + right_result = write_marker("PASSIVE.TXT", right_text, + (uint16_t)(sizeof(right_text) - 1u)); + } + /* Родитель должен вернуть собственный CWD даже из этого каталога. */ + (void)chdir(".."); + (void)chdir("EXECLEFT"); + + (void)settextmode(TEXT_MODE_40x32); + damage_commander_palette(); + clrscr_attr(0x27u); + textattr(0x2Fu); + gotoxy(0u, 0u); + cputs("P4CHILD.EXE\r\n\r\n"); + textattr(0x27u); + cputs("Child changed mode, palette and CWD.\r\n"); + cprintf("markers: active=%d passive=%d\r\n", left_result, right_result); + if (getcwd(child_cwd, (uint16_t)sizeof(child_cwd))) { + cprintf("child CWD: %s\r\n", child_cwd); + } + cputs("\r\nPress Enter to return code 5A..."); + (void)getkey(); + return 0x5A; +} diff --git a/applications/Volkov/tests/p4_exec_verify.py b/applications/Volkov/tests/p4_exec_verify.py new file mode 100755 index 0000000..6bcf28f --- /dev/null +++ b/applications/Volkov/tests/p4_exec_verify.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +"""Проверка файлов, созданных дочерней программой P4CHILD.""" + +from __future__ import annotations + +import pathlib +import sys + + +def main() -> None: + if len(sys.argv) != 2: + raise SystemExit(f"usage: {sys.argv[0]} EXTRACTED_ROOT") + root = pathlib.Path(sys.argv[1]) + expected = { + root / "EXECLEFT" / "CREATED.TXT": b"created by P4CHILD\n", + root / "EXECRGHT" / "PASSIVE.TXT": b"passive refresh by P4CHILD\n", + } + failures = [] + for path, content in expected.items(): + if not path.is_file(): + failures.append(f"нет {path.relative_to(root)}") + elif path.read_bytes() != content: + failures.append(f"неверное содержимое {path.relative_to(root)}") + if failures: + for failure in failures: + print(f"FAIL: {failure}") + raise SystemExit(1) + print("PASS: child создал active/passive markers с ожидаемым содержимым.") + + +if __name__ == "__main__": + main() diff --git a/applications/Volkov/tests/p4_fixture.py b/applications/Volkov/tests/p4_fixture.py new file mode 100755 index 0000000..3ac749b --- /dev/null +++ b/applications/Volkov/tests/p4_fixture.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +"""Создание и проверка детерминированного HDD-набора для P4 copy.""" + +from __future__ import annotations + +import pathlib +import sys + + +SOURCE_FILES = { + "CANCEL.BIN": (4 * 1024 * 1024 + 37, 0x31), + "EMPTY.ZRO": (0, 0x42), + "EXIST.TXT": (513, 0x53), + "F65535.BIN": (65535, 0x64), + "F65536.BIN": (65536, 0x75), + "K4095.BIN": (4095, 0x86), + "K4096.BIN": (4096, 0x97), + "LARGE.BIN": (131109, 0xA8), + "ONE.BIN": (1, 0xB9), +} +TARGET_EXISTING = b"TARGET MUST SURVIVE AN F5 REFUSAL\n" + + +def pattern(size: int, seed: int) -> bytes: + return bytes(((index * 37 + (index >> 8) * 11 + seed) & 0xFF) + for index in range(size)) + + +def create(root: pathlib.Path) -> None: + source = root / "COPYFROM" + target = root / "COPYTO" + source.mkdir(parents=True) + target.mkdir(parents=True) + for name, (size, seed) in SOURCE_FILES.items(): + (source / name).write_bytes(pattern(size, seed)) + (target / "EXIST.TXT").write_bytes(TARGET_EXISTING) + + +def verify_common(root: pathlib.Path) -> list[str]: + source = root / "COPYFROM" + target = root / "COPYTO" + failures: list[str] = [] + + for name, (size, seed) in SOURCE_FILES.items(): + path = source / name + expected = pattern(size, seed) + if not path.is_file() or path.read_bytes() != expected: + failures.append(f"повреждён источник {name}") + + if not (target / "EXIST.TXT").is_file(): + failures.append("нет исходного COPYTO/EXIST.TXT") + elif (target / "EXIST.TXT").read_bytes() != TARGET_EXISTING: + failures.append("существующий COPYTO/EXIST.TXT был изменён") + return failures + + +def verify_initial(root: pathlib.Path) -> None: + target = root / "COPYTO" + failures = verify_common(root) + + if (target / "LARGE.BIN").exists(): + failures.append("COPYTO/LARGE.BIN существует до теста") + if (target / "CANCEL.BIN").exists(): + failures.append("COPYTO/CANCEL.BIN существует до теста") + if list(target.glob("~SC*.TMP")): + failures.append("в исходном образе есть временный ~SC*.TMP") + + if failures: + for failure in failures: + print(f"FAIL: {failure}") + raise SystemExit(1) + print("PASS: исходный HDD-набор цел и готов к P4 copy-тесту.") + + +def verify(root: pathlib.Path) -> None: + target = root / "COPYTO" + failures = verify_common(root) + + large_expected = pattern(*SOURCE_FILES["LARGE.BIN"]) + if not (target / "LARGE.BIN").is_file(): + failures.append("нет скопированного COPYTO/LARGE.BIN") + elif (target / "LARGE.BIN").read_bytes() != large_expected: + failures.append("COPYTO/LARGE.BIN отличается от источника") + + if (target / "CANCEL.BIN").exists(): + failures.append("отменённый COPYTO/CANCEL.BIN остался на диске") + if not (target / "EMPTY.ZRO").is_file(): + failures.append("нет скопированного нулевого COPYTO/EMPTY.ZRO") + elif (target / "EMPTY.ZRO").stat().st_size != 0: + failures.append("COPYTO/EMPTY.ZRO имеет ненулевой размер") + if list(target.glob("~SC*.TMP")): + failures.append("после операции остался временный ~SC*.TMP") + + if failures: + for failure in failures: + print(f"FAIL: {failure}") + raise SystemExit(1) + print("PASS: LARGE/EMPTY скопированы; EXIST сохранён; cancel/temp отсутствуют.") + + +def main() -> None: + commands = {"create", "verify-initial", "verify"} + if len(sys.argv) != 3 or sys.argv[1] not in commands: + raise SystemExit( + f"usage: {sys.argv[0]} create|verify-initial|verify DIR") + root = pathlib.Path(sys.argv[2]) + if sys.argv[1] == "create": + create(root) + elif sys.argv[1] == "verify-initial": + verify_initial(root) + else: + verify(root) + + +if __name__ == "__main__": + main() diff --git a/applications/Volkov/tests/p5_copy_faults.c b/applications/Volkov/tests/p5_copy_faults.c new file mode 100644 index 0000000..553c1c5 --- /dev/null +++ b/applications/Volkov/tests/p5_copy_faults.c @@ -0,0 +1,186 @@ +/* + * p5_copy_faults.c — детерминированные отрицательные сценарии copy-job. + * + * Это не подмена интерактивного теста F5: программа вызывает то же банковое + * ядро копирования и останавливает его в состояниях, которые невозможно + * надёжно поймать клавишей с точностью до одного sc_copy_step(). + */ + +#include +#include +#include +#include +#include +#include +#include "sc_copy.h" +#include "sc_exec.h" + +static ScCopyJob job; +static int result_fd = -1; +static uint8_t failures; + +static void log_text(const char *text) +{ + const char *end = text; + uint16_t length; + + cputs(text); + while (*end != '\0') end++; + length = (uint16_t)(end - text); + if (result_fd >= 0) (void)write(result_fd, text, length); +} + +static void check(uint8_t condition, const char *name) +{ + if (condition) { + log_text("PASS "); + } else { + log_text("FAIL "); + failures++; + } + log_text(name); + log_text("\r\n"); +} + +static uint8_t path_absent(const char *path) +{ + int fd = open(path, O_RDONLY); + + if (fd < 0) return 1u; + (void)close(fd); + return 0u; +} + +static int write_small(const char *path, const char *data, uint16_t size) +{ + int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC); + int result = 0; + + if (fd < 0) return -1; + if (write(fd, data, size) != (int)size) result = -1; + if (close(fd) != 0) result = -1; + return result; +} + +int main(void) +{ + uint16_t total_before = 0u; + uint16_t free_before = 0u; + uint16_t total_after = 0u; + uint16_t free_after = 0u; + uint8_t block; + uint8_t page; + int step; + + textattr(0x07u); + clrscr_attr(0x07u); + cputs("P5 copy-job fault matrix\r\n\r\n"); + result_fd = open("D:\\RESULT.TXT", O_WRONLY | O_CREAT | O_TRUNC); + + mem_info(&total_before, &free_before); + block = mem_alloc_pages(1u); + if (block == 0u) { + log_text("FAIL EMM allocation\r\n"); + if (result_fd >= 0) (void)close(result_fd); + return 1; + } + page = mem_get_page(block, 0u); + + errno = 0; + step = sc_platform_exec("D:\\MISSING.EXE"); + check(step < 0 && errno == ENOENT, "missing EXE errno 3"); + + /* Temp создаётся раньше открытия source: ENOENT обязан убрать его. */ + check(sc_copy_begin(&job, "D:\\SOURCE", "D:\\TARGET", + "MISSING.BIN", 4096ul, page) != 0 && + job.state == SC_COPY_ERROR && + path_absent("D:\\TARGET\\MISSING.BIN"), + "missing source cleanup"); + + /* Отмена до первого чтения. */ + step = sc_copy_begin(&job, "D:\\SOURCE", "D:\\TARGET", + "BEGIN.BIN", 8192ul, page); + if (step == 0) sc_copy_cancel(&job); + check(step == 0 && job.state == SC_COPY_CANCELLED && job.done == 0ul && + path_absent("D:\\TARGET\\BEGIN.BIN"), + "cancel before first read"); + + /* Последний блок записан, но rename ещё не выполнялся: точка pre-commit. */ + step = sc_copy_begin(&job, "D:\\SOURCE", "D:\\TARGET", + "LATE.BIN", 4096ul, page); + if (step == 0) step = sc_copy_step(&job); + check(step == SC_COPY_STEP_MORE && job.state == SC_COPY_RUNNING && + job.done == job.total && path_absent("D:\\TARGET\\LATE.BIN"), + "last block stops before commit"); + sc_copy_cancel(&job); + check(job.state == SC_COPY_CANCELLED && + path_absent("D:\\TARGET\\LATE.BIN"), + "cancel at pre-commit"); + + /* Закрытый source fd даёт реальную ошибку bank_read_page(). */ + step = sc_copy_begin(&job, "D:\\SOURCE", "D:\\TARGET", + "READERR.BIN", 4096ul, page); + if (step == 0) { + (void)close(job.source_fd); + job.source_fd = -1; + step = sc_copy_step(&job); + } + check(step == SC_COPY_STEP_ERROR && job.state == SC_COPY_ERROR && + path_absent("D:\\TARGET\\READERR.BIN"), + "read error cleanup"); + + /* Реальный DSS READONLY: temp повторно открыт только для чтения, поэтому + * WRITE обязан вернуть errno 8, а job — удалить файл после close. */ + step = sc_copy_begin(&job, "D:\\SOURCE", "D:\\TARGET", + "RONLY.BIN", 4096ul, page); + if (step == 0) { + (void)close(job.temp_fd); + job.temp_fd = open(job.temp_path, O_RDONLY); + if (job.temp_fd >= 0) { + step = sc_copy_step(&job); + } else { + sc_copy_cancel(&job); + step = -2; + } + } + check(step == SC_COPY_STEP_ERROR && job.state == SC_COPY_ERROR && + job.error == EROFS && path_absent("D:\\TARGET\\RONLY.BIN"), + "readonly write cleanup"); + + /* Удаляем уже записанный temp перед commit: RENAME обязан отказать. */ + step = sc_copy_begin(&job, "D:\\SOURCE", "D:\\TARGET", + "RENAME.BIN", 4096ul, page); + if (step == 0) step = sc_copy_step(&job); + if (step == SC_COPY_STEP_MORE) { + (void)close(job.temp_fd); + job.temp_fd = -1; + (void)unlink(job.temp_path); + step = sc_copy_step(&job); + } + check(step == SC_COPY_STEP_ERROR && job.state == SC_COPY_ERROR && + path_absent("D:\\TARGET\\RENAME.BIN"), + "rename error cleanup"); + + /* Имитируем гонку: target появился после begin, но до commit. */ + step = sc_copy_begin(&job, "D:\\SOURCE", "D:\\TARGET", + "RACE.BIN", 4096ul, page); + if (step == 0) step = sc_copy_step(&job); + if (step == SC_COPY_STEP_MORE && + write_small("D:\\TARGET\\RACE.BIN", "KEEP", 4u) == 0) { + step = sc_copy_step(&job); + } + check(step == SC_COPY_STEP_ERROR && job.error == EEXIST, + "target race refuses overwrite"); + + mem_free_block(block); + mem_info(&total_after, &free_after); + check(total_before == total_after && free_before == free_after, + "EMM restored"); + + if (failures == 0u) log_text("ALL PASS\r\n"); + if (result_fd >= 0) (void)close(result_fd); + cputs("\r\nPress Enter to return to DSS.\r\n"); + while (kbhit()) (void)getkey(); + (void)getkey(); + return failures; +} diff --git a/applications/Volkov/tests/p5_copy_faults_fixture.py b/applications/Volkov/tests/p5_copy_faults_fixture.py new file mode 100755 index 0000000..cdfecf4 --- /dev/null +++ b/applications/Volkov/tests/p5_copy_faults_fixture.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +"""Создание и проверка HDD-набора отрицательных сценариев copy-job.""" + +from __future__ import annotations + +import pathlib +import sys + + +SIZES = { + "BEGIN.BIN": 8192, + "LATE.BIN": 4096, + "READERR.BIN": 4096, + "RONLY.BIN": 4096, + "RENAME.BIN": 4096, + "RACE.BIN": 4096, +} + + +def content(name: str, size: int) -> bytes: + seed = sum(name.encode("ascii")) & 0xFF + return bytes((seed + index * 29 + (index >> 7)) & 0xFF + for index in range(size)) + + +def create(root: pathlib.Path) -> None: + source = root / "SOURCE" + (root / "TARGET").mkdir(parents=True) + source.mkdir() + for name, size in SIZES.items(): + (source / name).write_bytes(content(name, size)) + + +def verify(root: pathlib.Path) -> None: + failures: list[str] = [] + source = root / "SOURCE" + target = root / "TARGET" + result = root / "RESULT.TXT" + + for name, size in SIZES.items(): + path = source / name + if not path.is_file() or path.read_bytes() != content(name, size): + failures.append(f"повреждён SOURCE/{name}") + + expected_lines = [ + "PASS missing EXE errno 3", + "PASS missing source cleanup", + "PASS cancel before first read", + "PASS last block stops before commit", + "PASS cancel at pre-commit", + "PASS read error cleanup", + "PASS readonly write cleanup", + "PASS rename error cleanup", + "PASS target race refuses overwrite", + "PASS EMM restored", + "ALL PASS", + ] + if not result.is_file(): + failures.append("не создан RESULT.TXT") + else: + text = result.read_bytes().decode("cp866", errors="replace") + for line in expected_lines: + if line not in text: + failures.append(f"нет строки результата: {line}") + + if not target.is_dir(): + failures.append("исчез TARGET") + else: + unexpected = sorted(path.name for path in target.iterdir() + if path.name.upper() != "RACE.BIN") + if unexpected: + failures.append("лишние файлы TARGET: " + ", ".join(unexpected)) + race = target / "RACE.BIN" + if not race.is_file() or race.read_bytes() != b"KEEP": + failures.append("RACE.BIN перезаписан или отсутствует") + temporaries = sorted(path.name for path in target.glob("~SC*.TMP")) + if temporaries: + failures.append("остались temp: " + ", ".join(temporaries)) + + if failures: + for failure in failures: + print(f"FAIL: {failure}") + raise SystemExit(1) + print("PASS: missing EXE, cancel, read/readonly/rename, race и EMM cleanup.") + + +def main() -> None: + if len(sys.argv) != 3 or sys.argv[1] not in {"create", "verify"}: + raise SystemExit(f"usage: {sys.argv[0]} create|verify DIR") + root = pathlib.Path(sys.argv[2]) + if sys.argv[1] == "create": + create(root) + else: + verify(root) + + +if __name__ == "__main__": + main() diff --git a/applications/Volkov/tests/p5_enospc_fixture.py b/applications/Volkov/tests/p5_enospc_fixture.py new file mode 100755 index 0000000..e2b10ec --- /dev/null +++ b/applications/Volkov/tests/p5_enospc_fixture.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +"""Набор почти полного HDD для проверки ENOSPC и удаления copy-temp.""" + +from __future__ import annotations + +import pathlib +import sys + + +SOURCE_SIZE = 262_181 +FILLER_SIZE = 32_850_000 + + +def source_content() -> bytes: + return bytes(((index * 37 + (index >> 8) * 11 + 0xA7) & 0xFF) + for index in range(SOURCE_SIZE)) + + +def write_filler(path: pathlib.Path) -> None: + chunk = bytes(((index * 17 + 0x53) & 0xFF) for index in range(65536)) + remaining = FILLER_SIZE + with path.open("wb") as stream: + while remaining: + part = min(remaining, len(chunk)) + stream.write(chunk[:part]) + remaining -= part + + +def create(root: pathlib.Path) -> None: + source = root / "SOURCE" + target = root / "TARGET" + source.mkdir(parents=True) + target.mkdir(parents=True) + (source / "FULL.BIN").write_bytes(source_content()) + write_filler(root / "FILLER.BIN") + + +def verify_initial(root: pathlib.Path) -> None: + failures = [] + source = root / "SOURCE" / "FULL.BIN" + target = root / "TARGET" + filler = root / "FILLER.BIN" + if not source.is_file() or source.read_bytes() != source_content(): + failures.append("повреждён SOURCE/FULL.BIN") + if not filler.is_file() or filler.stat().st_size != FILLER_SIZE: + failures.append("неверный FILLER.BIN") + if not target.is_dir() or list(target.iterdir()): + failures.append("TARGET не пуст до теста") + if failures: + for failure in failures: + print(f"FAIL: {failure}") + raise SystemExit(1) + print("PASS: ENOSPC fixture готов, SOURCE/FULL.BIN сохранён.") + + +def verify(root: pathlib.Path) -> None: + failures = [] + source = root / "SOURCE" / "FULL.BIN" + target = root / "TARGET" + filler = root / "FILLER.BIN" + if not source.is_file() or source.read_bytes() != source_content(): + failures.append("исходный FULL.BIN изменён") + if not filler.is_file() or filler.stat().st_size != FILLER_SIZE: + failures.append("FILLER.BIN изменён") + if (target / "FULL.BIN").exists(): + failures.append("после ENOSPC остался конечный TARGET/FULL.BIN") + temporaries = sorted(target.glob("~SC*.TMP")) + if temporaries: + failures.append("после ENOSPC остался временный файл") + if failures: + for failure in failures: + print(f"FAIL: {failure}") + raise SystemExit(1) + print("PASS: ENOSPC не изменил source/filler и не оставил target/temp.") + + +def main() -> None: + commands = {"create", "verify-initial", "verify"} + if len(sys.argv) != 3 or sys.argv[1] not in commands: + raise SystemExit( + f"usage: {sys.argv[0]} create|verify-initial|verify DIR") + root = pathlib.Path(sys.argv[2]) + if sys.argv[1] == "create": + create(root) + elif sys.argv[1] == "verify-initial": + verify_initial(root) + else: + verify(root) + + +if __name__ == "__main__": + main() diff --git a/applications/Volkov/tests/p5_fixture.py b/applications/Volkov/tests/p5_fixture.py new file mode 100755 index 0000000..70b7570 --- /dev/null +++ b/applications/Volkov/tests/p5_fixture.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +"""Детерминированный набор для P5: 20 copy и граничные размеры.""" + +from __future__ import annotations + +import pathlib +import sys + + +SIZES = [ + 0, + 1, + 4095, + 4096, + 65535, + 65536, + 37, + 511, + 512, + 513, + 1023, + 1024, + 2047, + 2048, + 8191, + 8192, + 16383, + 16384, + 32769, + 1024 * 1024 + 37, +] + + +def content(index: int, size: int) -> bytes: + seed = (0x31 + index * 13) & 0xFF + return bytes(((position * 37 + (position >> 8) * 11 + seed) & 0xFF) + for position in range(size)) + + +def create(root: pathlib.Path) -> None: + source = root / "SOURCE" + target = root / "TARGET" + source.mkdir(parents=True) + target.mkdir(parents=True) + for index, size in enumerate(SIZES): + (source / f"F{index:02d}.BIN").write_bytes(content(index, size)) + + +def verify_initial(root: pathlib.Path) -> None: + source = root / "SOURCE" + target = root / "TARGET" + failures = [] + for index, size in enumerate(SIZES): + path = source / f"F{index:02d}.BIN" + if not path.is_file() or path.read_bytes() != content(index, size): + failures.append(f"повреждён SOURCE/{path.name}") + if not target.is_dir() or list(target.iterdir()): + failures.append("TARGET не пуст до теста") + if failures: + for failure in failures: + print(f"FAIL: {failure}") + raise SystemExit(1) + print("PASS: P5 fixture содержит 20 источников, TARGET пуст.") + + +def verify(root: pathlib.Path) -> None: + source = root / "SOURCE" + target = root / "TARGET" + failures = [] + for index, size in enumerate(SIZES): + name = f"F{index:02d}.BIN" + source_path = source / name + target_path = target / name + if not target_path.is_file(): + failures.append(f"нет TARGET/{name}") + elif target_path.read_bytes() != source_path.read_bytes(): + failures.append(f"TARGET/{name} не совпадает с источником") + elif target_path.stat().st_size != size: + failures.append(f"неверный размер TARGET/{name}") + temporaries = sorted(target.glob("~SC*.TMP")) + if temporaries: + failures.append("остались временные файлы: " + + ", ".join(path.name for path in temporaries)) + if failures: + for failure in failures: + print(f"FAIL: {failure}") + raise SystemExit(1) + print("PASS: 20/20 файлов совпали; все граничные размеры и >1 МБ пройдены.") + + +def main() -> None: + commands = {"create", "verify-initial", "verify"} + if len(sys.argv) != 3 or sys.argv[1] not in commands: + raise SystemExit( + f"usage: {sys.argv[0]} create|verify-initial|verify DIR") + root = pathlib.Path(sys.argv[2]) + if sys.argv[1] == "create": + create(root) + elif sys.argv[1] == "verify-initial": + verify_initial(root) + else: + verify(root) + + +if __name__ == "__main__": + main() diff --git a/applications/Volkov/tests/p5_media_fixture.py b/applications/Volkov/tests/p5_media_fixture.py new file mode 100755 index 0000000..b0d69be --- /dev/null +++ b/applications/Volkov/tests/p5_media_fixture.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""Набор для отключения/возврата HDD во время F5.""" + +from __future__ import annotations + +import pathlib +import sys + + +SIZE = 262_181 + + +def data() -> bytes: + return bytes(((index * 41 + (index >> 9) * 7 + 0x35) & 0xFF) + for index in range(SIZE)) + + +def create(root: pathlib.Path) -> None: + media = root / "MEDIA" + (media / "DEEP").mkdir(parents=True) + (media / "DATA.BIN").write_bytes(data()) + (media / "DEEP" / "INNER.TXT").write_text( + "media-change fixture\n", encoding="ascii") + + +def verify(root: pathlib.Path) -> None: + failures: list[str] = [] + source = root / "MEDIA" / "DATA.BIN" + target = root / "DATA.BIN" + + if not source.is_file() or source.read_bytes() != data(): + failures.append("исходный MEDIA/DATA.BIN повреждён") + if not target.is_file() or target.read_bytes() != data(): + failures.append("DATA.BIN не скопирован после возврата HDD") + temporaries = sorted(root.glob("~SC*.TMP")) + if temporaries: + failures.append("после media-change остался временный файл") + inner = root / "MEDIA" / "DEEP" / "INNER.TXT" + if not inner.is_file() or inner.read_bytes() != b"media-change fixture\n": + failures.append("вложенный контрольный файл повреждён") + + if failures: + for failure in failures: + print(f"FAIL: {failure}") + raise SystemExit(1) + print("PASS: HDD removal дал управляемый отказ; после возврата DATA совпал, temp нет.") + + +def main() -> None: + if len(sys.argv) != 3 or sys.argv[1] not in {"create", "verify"}: + raise SystemExit(f"usage: {sys.argv[0]} create|verify DIR") + root = pathlib.Path(sys.argv[2]) + if sys.argv[1] == "create": + create(root) + else: + verify(root) + + +if __name__ == "__main__": + main() diff --git a/applications/Volkov/tests/p6_sort_fixture.py b/applications/Volkov/tests/p6_sort_fixture.py new file mode 100755 index 0000000..ee5adca --- /dev/null +++ b/applications/Volkov/tests/p6_sort_fixture.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +"""Создаёт каталоги UI-пробы сортировки и проверяет target-результат.""" + +from __future__ import annotations + +import os +import pathlib +import sys +from datetime import datetime + + +FILES = { + "ALPHA.BIN": (50, datetime(2025, 1, 2, 12, 0, 0)), + "BETA.TXT": (20, datetime(2023, 1, 2, 12, 0, 0)), + "GAMMA.TXT": (10, datetime(2024, 1, 2, 12, 0, 0)), + "DELTA.EXE": (30, datetime(2022, 1, 2, 12, 0, 0)), +} + + +def create(root: pathlib.Path) -> None: + for panel_name in ("SORTL", "SORTR"): + panel = root / panel_name + panel.mkdir(parents=True) + for directory in ("ADIR", "ZDIR"): + (panel / directory).mkdir() + for name, (size, stamp) in FILES.items(): + path = panel / name + path.write_bytes(bytes((index * 37 + size) & 0xFF + for index in range(size))) + timestamp = stamp.timestamp() + os.utime(path, (timestamp, timestamp)) + + +def verify(root: pathlib.Path) -> None: + result = root / "SORTRES.TXT" + expected = [ + "PASS name ascending and fixed groups", + "PASS selection fixture points to BETA", + "PASS name descending toggle", + "PASS selection preserved after sort", + "PASS extension ascending", + "PASS extension descending toggle", + "PASS size ascending", + "PASS size descending toggle", + "PASS date ascending", + "PASS date descending toggle", + "PASS panel sort modes are independent", + "PASS second EMM page sorted independently", + "ALL PASS", + ] + failures: list[str] = [] + if not result.is_file(): + failures.append("нет SORTRES.TXT") + else: + text = result.read_bytes().decode("cp866", errors="replace") + for line in expected: + if line not in text: + failures.append(f"нет строки результата: {line}") + if failures: + for failure in failures: + print(f"FAIL: {failure}") + raise SystemExit(1) + print("PASS: 4 sort keys, reverse, fixed groups, selection, two panels.") + + +def main() -> None: + if len(sys.argv) != 3 or sys.argv[1] not in {"create", "verify"}: + raise SystemExit(f"usage: {sys.argv[0]} create|verify DIR") + root = pathlib.Path(sys.argv[2]) + if sys.argv[1] == "create": + create(root) + else: + verify(root) + + +if __name__ == "__main__": + main() diff --git a/applications/Volkov/tests/p6_sort_probe.c b/applications/Volkov/tests/p6_sort_probe.c new file mode 100644 index 0000000..b87fe91 --- /dev/null +++ b/applications/Volkov/tests/p6_sort_probe.c @@ -0,0 +1,201 @@ +/* + * p6_sort_probe.c — target-регрессия четырёх ключей сортировки панели. + * + * Проверяет реальный EMM-store и банковый sc_sort.c. Записи намеренно + * перемешаны; `..` и маркер DSS обязаны сохранять крайние позиции, а + * каталоги — оставаться перед файлами при любом направлении. + */ + +#include +#include +#include +#include +#include +#include +#include "sc_panel.h" +#include "sc_sort.h" +#include "sc_store.h" + +static int result_fd = -1; +static uint8_t failures; + +static void log_text(const char *text) +{ + const char *end = text; + uint16_t length; + + while (*end != '\0') end++; + length = (uint16_t)(end - text); + if (result_fd >= 0) (void)write(result_fd, text, length); +} + +static void check(uint8_t condition, const char *name) +{ + log_text(condition ? "PASS " : "FAIL "); + if (!condition) failures++; + log_text(name); + log_text("\r\n"); +} + +/* sc_sort_set() вызывает общий panel-инвариант. В probe достаточно той же + * нормализации cursor/top без подтягивания файлового source-модуля. */ +void sc_panel_normalize_view(ScPanel *panel) +{ + if (panel->count == 0u) { + panel->cursor = 0u; + panel->top = 0u; + return; + } + if (panel->cursor >= panel->count) panel->cursor = panel->count - 1u; + if (panel->top > panel->cursor) panel->top = panel->cursor; + if (panel->cursor >= (uint16_t)(panel->top + 27u)) { + panel->top = (uint16_t)(panel->cursor - 26u); + } +} + +static void put_entry(ScPanel *panel, uint16_t index, const char *name, + uint32_t size, uint16_t date, uint8_t attr, + uint8_t flags) +{ + ScDirEntry entry; + + memset(&entry, 0, sizeof(entry)); + strcpy(entry.name, name); + entry.size = size; + entry.date = date; + entry.time = (uint16_t)(index << 5); + entry.attr = attr; + entry.flags = flags; + (void)sc_store_put(panel, index, &entry); +} + +static void fill_panel(ScPanel *panel, uint8_t page) +{ + memset(panel, 0, sizeof(*panel)); + panel->phys_page = page; + panel->count = 8u; + panel->sort_key = SC_SORT_NAME; + put_entry(panel, 0u, "BETA.TXT", 20ul, 0x5621u, 0u, 0u); + put_entry(panel, 1u, ">>> MORE...", 0ul, 0u, 0u, + SC_ENTRY_DSS_LIMIT); + put_entry(panel, 2u, "ZDIR", 0ul, 0x5A21u, FA_DIREC, 0u); + put_entry(panel, 3u, "ALPHA.BIN", 50ul, 0x5A21u, 0u, 0u); + put_entry(panel, 4u, "..", 0ul, 0u, FA_DIREC, + SC_ENTRY_PARENT); + put_entry(panel, 5u, "GAMMA.TXT", 10ul, 0x5821u, 0u, 0u); + put_entry(panel, 6u, "ADIR", 0ul, 0x5421u, FA_DIREC, 0u); + put_entry(panel, 7u, "DELTA.EXE", 30ul, 0x5421u, 0u, 0u); +} + +static uint8_t order_is(const ScPanel *panel, const char *const *names) +{ + ScDirEntry entry; + uint16_t i; + + for (i = 0u; i < panel->count; i++) { + if (sc_store_get(panel, i, &entry) != 0 || + strcmp(entry.name, names[i]) != 0) return 0u; + } + return 1u; +} + +static uint8_t selected_is(const ScPanel *panel, const char *name) +{ + ScDirEntry entry; + return sc_store_get(panel, panel->cursor, &entry) == 0 && + strcmp(entry.name, name) == 0; +} + +int main(void) +{ + static const char *const name_up[] = { + "..", "ADIR", "ZDIR", "ALPHA.BIN", "BETA.TXT", "DELTA.EXE", + "GAMMA.TXT", ">>> MORE..." + }; + static const char *const name_down[] = { + "..", "ZDIR", "ADIR", "GAMMA.TXT", "DELTA.EXE", "BETA.TXT", + "ALPHA.BIN", ">>> MORE..." + }; + static const char *const ext_up[] = { + "..", "ADIR", "ZDIR", "ALPHA.BIN", "DELTA.EXE", "BETA.TXT", + "GAMMA.TXT", ">>> MORE..." + }; + static const char *const ext_down[] = { + "..", "ZDIR", "ADIR", "GAMMA.TXT", "BETA.TXT", "DELTA.EXE", + "ALPHA.BIN", ">>> MORE..." + }; + static const char *const size_up[] = { + "..", "ADIR", "ZDIR", "GAMMA.TXT", "BETA.TXT", "DELTA.EXE", + "ALPHA.BIN", ">>> MORE..." + }; + static const char *const size_down[] = { + "..", "ZDIR", "ADIR", "ALPHA.BIN", "DELTA.EXE", "BETA.TXT", + "GAMMA.TXT", ">>> MORE..." + }; + static const char *const date_up[] = { + "..", "ADIR", "ZDIR", "DELTA.EXE", "BETA.TXT", "GAMMA.TXT", + "ALPHA.BIN", ">>> MORE..." + }; + static const char *const date_down[] = { + "..", "ZDIR", "ADIR", "ALPHA.BIN", "GAMMA.TXT", "BETA.TXT", + "DELTA.EXE", ">>> MORE..." + }; + ScPanel left; + ScPanel right; + uint8_t block; + uint8_t left_page; + uint8_t right_page; + + result_fd = open("D:\\SORTRES.TXT", O_WRONLY | O_CREAT | O_TRUNC); + block = mem_alloc_pages(2u); + if (block == 0u) { + log_text("FAIL EMM allocation\r\n"); + if (result_fd >= 0) (void)close(result_fd); + return 1; + } + left_page = mem_get_page(block, 0u); + right_page = mem_get_page(block, 1u); + fill_panel(&left, left_page); + fill_panel(&right, right_page); + + check(sc_sort_panel(&left) == 0 && order_is(&left, name_up), + "name ascending and fixed groups"); + left.cursor = 4u; /* BETA.TXT в уже отсортированном списке. */ + check(selected_is(&left, "BETA.TXT"), + "selection fixture points to BETA"); + + check(sc_sort_set(&left, SC_SORT_NAME) == 0 && + left.sort_descending && order_is(&left, name_down), + "name descending toggle"); + check(selected_is(&left, "BETA.TXT"), + "selection preserved after sort"); + check(sc_sort_set(&left, SC_SORT_EXTENSION) == 0 && + !left.sort_descending && order_is(&left, ext_up), + "extension ascending"); + check(sc_sort_set(&left, SC_SORT_EXTENSION) == 0 && + left.sort_descending && order_is(&left, ext_down), + "extension descending toggle"); + check(sc_sort_set(&left, SC_SORT_SIZE) == 0 && + !left.sort_descending && order_is(&left, size_up), + "size ascending"); + check(sc_sort_set(&left, SC_SORT_SIZE) == 0 && + left.sort_descending && order_is(&left, size_down), + "size descending toggle"); + check(sc_sort_set(&left, SC_SORT_DATE) == 0 && + !left.sort_descending && order_is(&left, date_up), + "date ascending"); + check(sc_sort_set(&left, SC_SORT_DATE) == 0 && + left.sort_descending && order_is(&left, date_down), + "date descending toggle"); + + check(sc_sort_set(&right, SC_SORT_EXTENSION) == 0 && + right.sort_key == SC_SORT_EXTENSION && !right.sort_descending && + left.sort_key == SC_SORT_DATE && left.sort_descending, + "panel sort modes are independent"); + check(order_is(&right, ext_up), "second EMM page sorted independently"); + + mem_free_block(block); + if (failures == 0u) log_text("ALL PASS\r\n"); + if (result_fd >= 0) (void)close(result_fd); + return failures; +} diff --git a/applications/Volkov/tests/p7_select_fixture.py b/applications/Volkov/tests/p7_select_fixture.py new file mode 100755 index 0000000..fa639e4 --- /dev/null +++ b/applications/Volkov/tests/p7_select_fixture.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +"""Создаёт UI-каталог выбора и проверяет SELRES.TXT target-пробы.""" + +from __future__ import annotations + +import pathlib +import sys + + +def create(root: pathlib.Path) -> None: + panel = root / "SELECT" + panel.mkdir(parents=True) + (panel / "ADIR").mkdir() + (panel / "ZDIR").mkdir() + for name, size in (("A.BIN", 10), ("B.BIN", 20), ("C.BIN", 30)): + (panel / name).write_bytes(bytes((index * 19 + size) & 0xFF + for index in range(size))) + + +def verify(root: pathlib.Path) -> None: + result = root / "SELRES.TXT" + expected = [ + "PASS parent cannot be selected", + "PASS Insert selects file and advances", + "PASS second Insert accumulates size", + "PASS DSS marker cannot be selected", + "PASS invert changes files only", + "PASS second invert restores file selection", + "PASS individual Insert may select directory", + "PASS clear group ignores selected directory", + "PASS mask is ASCII case insensitive", + "PASS question wildcard clears matching files", + "PASS DOS star-dot-star selects all files only", + "PASS group mask never changes directory flag", + "PASS refresh clear resets selection totals", + "PASS EMM restored", + "ALL PASS", + ] + failures: list[str] = [] + if not result.is_file(): + failures.append("нет SELRES.TXT") + else: + text = result.read_bytes().decode("cp866", errors="replace") + for line in expected: + if line not in text: + failures.append(f"нет строки результата: {line}") + if failures: + for failure in failures: + print(f"FAIL: {failure}") + raise SystemExit(1) + print("PASS: Insert/invert/masks, virtual guards, totals, refresh и EMM.") + + +def main() -> None: + if len(sys.argv) != 3 or sys.argv[1] not in {"create", "verify"}: + raise SystemExit(f"usage: {sys.argv[0]} create|verify DIR") + root = pathlib.Path(sys.argv[2]) + if sys.argv[1] == "create": + create(root) + else: + verify(root) + + +if __name__ == "__main__": + main() diff --git a/applications/Volkov/tests/p7_select_probe.c b/applications/Volkov/tests/p7_select_probe.c new file mode 100644 index 0000000..4e1902f --- /dev/null +++ b/applications/Volkov/tests/p7_select_probe.c @@ -0,0 +1,162 @@ +/* + * p7_select_probe.c — target-регрессия выбора записей панели. + * + * Проверяет Insert/invert через настоящий EMM-store, статистику выбора и + * запрет синтетических записей без зависимости от UI-таймингов. + */ + +#include +#include +#include +#include +#include +#include +#include "sc_panel.h" +#include "sc_select.h" +#include "sc_store.h" + +static int result_fd = -1; +static uint8_t failures; + +static void log_text(const char *text) +{ + const char *end = text; + while (*end != '\0') end++; + if (result_fd >= 0) { + (void)write(result_fd, text, (uint16_t)(end - text)); + } +} + +static void check(uint8_t condition, const char *name) +{ + log_text(condition ? "PASS " : "FAIL "); + if (!condition) failures++; + log_text(name); + log_text("\r\n"); +} + +void sc_panel_normalize_view(ScPanel *panel) +{ + if (panel->count == 0u) { + panel->cursor = 0u; + panel->top = 0u; + return; + } + if (panel->cursor >= panel->count) panel->cursor = panel->count - 1u; + if (panel->top > panel->cursor) panel->top = panel->cursor; + if (panel->cursor >= (uint16_t)(panel->top + 27u)) { + panel->top = (uint16_t)(panel->cursor - 26u); + } +} + +static void put_entry(ScPanel *panel, uint16_t index, const char *name, + uint32_t size, uint8_t attr, uint8_t flags) +{ + ScDirEntry entry; + memset(&entry, 0, sizeof(entry)); + strcpy(entry.name, name); + entry.size = size; + entry.attr = attr; + entry.flags = flags; + (void)sc_store_put(panel, index, &entry); +} + +static uint8_t selected(const ScPanel *panel, uint16_t index) +{ + ScDirEntry entry; + return sc_store_get(panel, index, &entry) == 0 && + (entry.flags & SC_ENTRY_SELECTED) != 0u; +} + +int main(void) +{ + ScPanel panel; + uint16_t total_before = 0u; + uint16_t free_before = 0u; + uint16_t total_after = 0u; + uint16_t free_after = 0u; + uint8_t block; + uint8_t page; + + result_fd = open("D:\\SELRES.TXT", O_WRONLY | O_CREAT | O_TRUNC); + mem_info(&total_before, &free_before); + block = mem_alloc_pages(1u); + page = block ? mem_get_page(block, 0u) : 0u; + if (!block || !page) { + log_text("FAIL EMM allocation\r\n"); + if (result_fd >= 0) (void)close(result_fd); + return 1; + } + + memset(&panel, 0, sizeof(panel)); + panel.phys_page = page; + panel.count = 5u; + put_entry(&panel, 0u, "..", 0ul, FA_DIREC, SC_ENTRY_PARENT); + put_entry(&panel, 1u, "ADIR", 0ul, FA_DIREC, 0u); + put_entry(&panel, 2u, "A.BIN", 10ul, 0u, 0u); + put_entry(&panel, 3u, "B.BIN", 20ul, 0u, 0u); + put_entry(&panel, 4u, ">>> MORE...", 0ul, 0u, SC_ENTRY_DSS_LIMIT); + + check(sc_select_apply(&panel, SC_SELECT_TOGGLE, 0) == 1 && + panel.cursor == 0u && + panel.selected_count == 0u, "parent cannot be selected"); + + panel.cursor = 2u; + check(sc_select_apply(&panel, SC_SELECT_TOGGLE, 0) == 0 && + panel.cursor == 3u && + panel.selected_count == 1u && panel.selected_size == 10ul && + selected(&panel, 2u), "Insert selects file and advances"); + check(sc_select_apply(&panel, SC_SELECT_TOGGLE, 0) == 0 && + panel.cursor == 4u && + panel.selected_count == 2u && panel.selected_size == 30ul && + selected(&panel, 3u), "second Insert accumulates size"); + check(sc_select_apply(&panel, SC_SELECT_TOGGLE, 0) == 1 && + panel.cursor == 4u && + panel.selected_count == 2u && !selected(&panel, 4u), + "DSS marker cannot be selected"); + + check(sc_select_apply(&panel, SC_SELECT_INVERT, 0) == 0 && + panel.selected_count == 0u && panel.selected_size == 0ul && + !selected(&panel, 1u) && !selected(&panel, 2u) && + !selected(&panel, 3u), "invert changes files only"); + check(sc_select_apply(&panel, SC_SELECT_INVERT, 0) == 0 && + panel.selected_count == 2u && panel.selected_size == 30ul && + !selected(&panel, 1u) && selected(&panel, 2u) && + selected(&panel, 3u), "second invert restores file selection"); + + panel.cursor = 1u; + check(sc_select_apply(&panel, SC_SELECT_TOGGLE, 0) == 0 && + panel.selected_count == 3u && panel.selected_size == 30ul && + selected(&panel, 1u), "individual Insert may select directory"); + check(sc_select_apply(&panel, SC_SELECT_MASK_CLEAR, "*") == 0 && + panel.selected_count == 1u && panel.selected_size == 0ul && + selected(&panel, 1u), "clear group ignores selected directory"); + check(sc_select_apply(&panel, SC_SELECT_MASK_SET, "*.bin") == 0 && + panel.selected_count == 3u && panel.selected_size == 30ul && + selected(&panel, 1u) && + selected(&panel, 2u) && selected(&panel, 3u), + "mask is ASCII case insensitive"); + check(sc_select_apply(&panel, SC_SELECT_MASK_CLEAR, "?.b?n") == 0 && + panel.selected_count == 1u && panel.selected_size == 0ul && + selected(&panel, 1u), + "question wildcard clears matching files"); + check(sc_select_apply(&panel, SC_SELECT_MASK_SET, "*.*") == 0 && + panel.selected_count == 3u && panel.selected_size == 30ul && + selected(&panel, 1u), "DOS star-dot-star selects all files only"); + check(sc_select_apply(&panel, SC_SELECT_MASK_CLEAR, "A*") == 0 && + panel.selected_count == 2u && panel.selected_size == 20ul && + selected(&panel, 1u) && !selected(&panel, 2u) && + selected(&panel, 3u), "group mask never changes directory flag"); + + sc_store_clear(&panel); + check(panel.count == 0u && panel.selected_count == 0u && + panel.selected_size == 0ul, "refresh clear resets selection totals"); + + mem_free_block(block); + mem_info(&total_after, &free_after); + check(total_before == total_after && free_before == free_after, + "EMM restored"); + if (failures == 0u) log_text("ALL PASS\r\n"); + if (result_fd >= 0) (void)close(result_fd); + return failures; +} diff --git a/applications/Volkov/tests/p9_mkdir_fixture.py b/applications/Volkov/tests/p9_mkdir_fixture.py new file mode 100755 index 0000000..8459b43 --- /dev/null +++ b/applications/Volkov/tests/p9_mkdir_fixture.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +"""Создаёт и проверяет HDD-fixture команды F7 mkdir.""" + +from __future__ import annotations + +import pathlib +import sys + + +def create(root: pathlib.Path) -> None: + panel = root / "MKTEST" + panel.mkdir(parents=True) + (panel / "KEEP.TXT").write_bytes(b"must remain\r\n") + + +def verify(root: pathlib.Path) -> None: + expected = [ + "PASS valid DOS 8.3 components accepted", + "PASS length and dot violations rejected", + "PASS path injection and wildcard rejected", + "PASS validated component joins full path", + "PASS CWD captured before mkdir", + "PASS mkdir creates directory", + "PASS duplicate mkdir is managed error", + "PASS rmdir removes probe directory", + "PASS mkdir/rmdir preserve process CWD", + "ALL PASS", + ] + failures: list[str] = [] + result = root / "MKRES.TXT" + panel = root / "MKTEST" + + if not result.is_file(): + failures.append("нет MKRES.TXT") + else: + text = result.read_bytes().decode("cp866", errors="replace") + for line in expected: + if line not in text: + failures.append(f"нет строки результата: {line}") + if not (panel / "NEWDIR").is_dir(): + failures.append("F7 не создал MKTEST/NEWDIR") + elif any((panel / "NEWDIR").iterdir()): + failures.append("NEWDIR неожиданно не пуст") + if not (panel / "KEEP.TXT").is_file() or \ + (panel / "KEEP.TXT").read_bytes() != b"must remain\r\n": + failures.append("KEEP.TXT изменён") + for unwanted in ("PROBE", "TOOLONG9", "TOOLONG99"): + if (panel / unwanted).exists(): + failures.append(f"остался лишний MKTEST/{unwanted}") + if failures: + for failure in failures: + print(f"FAIL: {failure}") + raise SystemExit(1) + print("PASS: validator, mkdir/error/rmdir/CWD и F7 NEWDIR.") + + +def main() -> None: + if len(sys.argv) != 3 or sys.argv[1] not in {"create", "verify"}: + raise SystemExit(f"usage: {sys.argv[0]} create|verify DIR") + root = pathlib.Path(sys.argv[2]) + if sys.argv[1] == "create": + create(root) + else: + verify(root) + + +if __name__ == "__main__": + main() diff --git a/applications/Volkov/tests/p9_mkdir_probe.c b/applications/Volkov/tests/p9_mkdir_probe.c new file mode 100644 index 0000000..784ffde --- /dev/null +++ b/applications/Volkov/tests/p9_mkdir_probe.c @@ -0,0 +1,81 @@ +/* + * p9_mkdir_probe.c — target-регрессия validator и libc mkdir/rmdir. + */ + +#include +#include +#include +#include +#include +#include "sc_path.h" + +static int result_fd = -1; +static uint8_t failures; +static char cwd_before[256]; +static char cwd_after[256]; +static char joined[256]; + +static void log_text(const char *text) +{ + const char *end = text; + while (*end != '\0') end++; + if (result_fd >= 0) { + (void)write(result_fd, text, (uint16_t)(end - text)); + } +} + +static void check(uint8_t condition, const char *name) +{ + log_text(condition ? "PASS " : "FAIL "); + if (!condition) failures++; + log_text(name); + log_text("\r\n"); +} + +int main(void) +{ + int rc; + + result_fd = open("D:\\MKRES.TXT", O_WRONLY | O_CREAT | O_TRUNC); + check(sc_path_validate_component("A") == 0 && + sc_path_validate_component("ABCDEFGH") == 0 && + sc_path_validate_component("ABCDEFGH.XYZ") == 0 && + sc_path_validate_component("A_B-1.2") == 0, + "valid DOS 8.3 components accepted"); + check(sc_path_validate_component("") != 0 && + sc_path_validate_component(".") != 0 && + sc_path_validate_component("..") != 0 && + sc_path_validate_component("ABCDEFGHI") != 0 && + sc_path_validate_component("A.ABCD") != 0 && + sc_path_validate_component("A.B.C") != 0, + "length and dot violations rejected"); + check(sc_path_validate_component("BAD\\NAME") != 0 && + sc_path_validate_component("BAD/NAME") != 0 && + sc_path_validate_component("*.BIN") != 0 && + sc_path_validate_component("BAD NAME") != 0, + "path injection and wildcard rejected"); + check(sc_path_join(joined, sizeof(joined), + "D:\\MKTEST", "NEWDIR") == 0 && + strcmp(joined, "D:\\MKTEST\\NEWDIR") == 0, + "validated component joins full path"); + + check(getcwd(cwd_before, sizeof(cwd_before)) != 0, + "CWD captured before mkdir"); + (void)rmdir("D:\\MKTEST\\PROBE"); + errno = 0; + rc = mkdir("D:\\MKTEST\\PROBE"); + check(rc == 0, "mkdir creates directory"); + errno = 0; + rc = mkdir("D:\\MKTEST\\PROBE"); + check(rc != 0 && (errno == EEXIST || errno == EISDIR), + "duplicate mkdir is managed error"); + check(rmdir("D:\\MKTEST\\PROBE") == 0, + "rmdir removes probe directory"); + check(getcwd(cwd_after, sizeof(cwd_after)) != 0 && + strcmp(cwd_before, cwd_after) == 0, + "mkdir/rmdir preserve process CWD"); + + if (failures == 0u) log_text("ALL PASS\r\n"); + if (result_fd >= 0) (void)close(result_fd); + return failures; +} diff --git a/applications/Volkov/tests/run_mame_hdd.sh b/applications/Volkov/tests/run_mame_hdd.sh new file mode 100755 index 0000000..c9dca06 --- /dev/null +++ b/applications/Volkov/tests/run_mame_hdd.sh @@ -0,0 +1,32 @@ +#!/bin/sh +# Запускает MAME с локальным тестовым CHD как hard2 (D:) и заданным Lua. +# Скрипт не пересобирает образ и не чистит каталог кадров. + +set -eu + +if [ "$#" -lt 3 ] || [ "$#" -gt 4 ]; then + echo "usage: $0 IMAGE.chd SCENARIO.lua SNAPSHOT_DIR [TIMEOUT]" >&2 + exit 1 +fi + +PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +MAME_DIR="$PROJECT_DIR/../../mame/v306" +IMAGE=$(CDPATH= cd -- "$(dirname -- "$1")" && pwd)/$(basename -- "$1") +LUA=$(CDPATH= cd -- "$(dirname -- "$2")" && pwd)/$(basename -- "$2") +SNAPSHOT_DIR=$(mkdir -p "$3" && CDPATH= cd -- "$3" && pwd) +TIMEOUT=${4:-75} + +if pgrep -fl 'mame[^ ]* sprinter( |$)' | grep . >/dev/null 2>&1; then + echo "run_mame_hdd.sh: уже запущен MAME sprinter" >&2 + exit 1 +fi + +cd "$MAME_DIR" +exec timeout "$TIMEOUT" ./mame.arm sprinter \ + -skip_gameinfo -video opengl -window -nofilter \ + -beta:wd179x:0 35hd -beta:wd179x:1 35hd \ + -flop1 ./IMG/mc.img -flop2 ./IMG/dss171u.img \ + -isa0 zxbus_adapter -isa0:zxbus_adapter:card neogs \ + -hard1 ./IMG/sp_hdd_sys.chd -hard2 "$IMAGE" \ + -ata2:0 cdrom -cdrom ./IMG/SprinterCD.iso -bios v3.06 \ + -snapshot_directory "$SNAPSHOT_DIR" -autoboot_script "$LUA" diff --git a/docs/TODO.md b/docs/TODO.md index 9c77389..df68f5f 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -5,6 +5,16 @@ ## Ближайшее +- [ ] **Дополнить `` полным списком кодов DSS после 32.** + Исходники DSS 1.71 подтверждают как минимум код 35 + `TOO_MANY_FILES_IN_DIR`: `F_FIRST/F_NEXT` возвращают его в `A` при + `CF=1`, а libc правильно переносит число в `errno`, но публичного + символического имени и строки `strerror()` сейчас нет. Сверить весь + список с `Shared_Includes/DSS_Errors.inc`, добавить константы без + перенумерации существующих кодов, расширить таблицу `strerror`, + `docs/libc-reference.md` и тест на преобразование `CF/A -> errno`. + До этого Commander использует локальную константу со значением 35. + - [ ] **Перевести новые EMM/W0-функции libc/libbgi на Z80 asm:** `bank_load_file()`, `bank_save_file()` и `gfx_w0_page_prepare()`. Сейчас универсальная C-обвязка (четыре аргумента, IX-frame, проверки diff --git a/docs/libc-reference.md b/docs/libc-reference.md index 428ed4a..75284d5 100644 --- a/docs/libc-reference.md +++ b/docs/libc-reference.md @@ -483,10 +483,12 @@ inline `sprinter_page_w0..w3(page)`; ENV: `getenv putenv sysenv`. mem_info(&total,&free)` (реализации _bios/_estex; макро-выбор MEM_MANAGE_MODE_*). HOME-резидентный доступ к чужим страницам: `bank_load_byte/store_byte/read/write` (своп W3 внутри; *_w1 — -вариант через окно W1). `bank_load_file(page,off,path,max_size)` читает -файл непосредственно в диапазон EMM-страницы, `bank_save_file` записывает -диапазон одним WRITE; обе функции возвращают фактический размер либо -1. -Они восстанавливают W3 до возврата и безопасны для вызова из huge-банка. +вариант через окно W1). `bank_read_page(fd,page,off,size)` и +`bank_write_page(fd,page,off,size)` обмениваются данными с уже открытым +файлом в его текущей позиции. `bank_load_file(page,off,path,max_size)` сам +открывает файл для чтения, а `bank_save_file` создаёт файл и записывает +диапазон одним WRITE. Файловые функции возвращают фактический размер либо +-1, восстанавливают W3 до возврата и безопасны для вызова из huge-банка. ## diff --git a/examples/mdview2/mdview2.c b/examples/mdview2/mdview2.c index 586b3a7..b228b46 100644 --- a/examples/mdview2/mdview2.c +++ b/examples/mdview2/mdview2.c @@ -215,6 +215,7 @@ static uint16_t g_last_prog; /* n_lines на последнем /* Кодировки (ENC_*) и g_encoding — в mdview2.h / mdview2_enc.c. */ const uint8_t *g_remap; /* extern: активная таблица ремапа (CP1251/KOI8), 0 = без ремапа (CP866)*/ uint8_t g_scratch_phys; /* extern: EMM-страница под ремап-буфер строки для win_rest */ +static uint8_t g_scratch_blk; /* handle scratch-блока: нужен для освобождения при exit */ /* UTF-8 набор (utf_blk/utf_pages/utf_phys/utf_size/utf_avail, конвертер и его * состояние) — в mdview2_enc.c; разделяемые символы и API — в mdview2.h. @@ -562,6 +563,7 @@ static void unload_file(void) } if (orig_file_blk) { mem_free_block(orig_file_blk); orig_file_blk = 0; } if (utf_blk) { mem_free_block(utf_blk); utf_blk = 0; } + if (g_scratch_blk) { mem_free_block(g_scratch_blk); g_scratch_blk = 0; } file_blk = 0; } @@ -1108,8 +1110,18 @@ int main(int argc, char **argv) cputs("\n\r\n\r"); exit(-1); } - /* Scratch-страница под ремап-буфер строки (win_rest_remap). */ - g_scratch_phys = mem_get_page(mem_alloc_pages(1), 0); + /* Scratch-страница под ремап-буфер строки (win_rest_remap). Handle + * сохраняем отдельно: физический номер страницы нельзя передавать + * mem_free_block(), а on_exit() обязан вернуть EMM системе. */ + g_scratch_blk = mem_alloc_pages(1); + if (g_scratch_blk == 0) { + spinner_show(0); + clrscr_attr(ATTR_RESET); + textattr(ATTR_RESET); + cputs("MDView: scratch page allocation failed\n\r"); + exit(-1); + } + g_scratch_phys = mem_get_page(g_scratch_blk, 0); /* Оба набора пока не построены (страховка к инициализатору g_doc). */ g_doc[DOC_8BIT].built = 0; diff --git a/libc/include/fcntl.h b/libc/include/fcntl.h index 1b7acad..4b8dd06 100644 --- a/libc/include/fcntl.h +++ b/libc/include/fcntl.h @@ -1,9 +1,10 @@ /* * fcntl.h — open / creat for Sprinter ESTEX. * - * POSIX-style flag bits. The low two bits select access mode (matches - * POSIX numbering — RDONLY=0, WRONLY=1, RDWR=2 — and is translated to - * the ESTEX OPEN $11 convention inside open()). + * Флаги унаследованы от исторического Sprinter C API: младшие биты задают + * RDONLY=2, WRONLY=1, RDWR=3 (это НЕ POSIX-нумерация). open() переводит их + * в соглашение ESTEX OPEN $11; нулевой access mode принимается как RDONLY + * для совместимости с кодом, ожидающим POSIX O_RDONLY=0. * * The other flags map onto ESTEX calls like this: * O_CREAT + O_EXCL → $0B (CREATE_NEW, fails if exists) diff --git a/libc/include/sprinter_mem.h b/libc/include/sprinter_mem.h index 1da753e..a72c03c 100644 --- a/libc/include/sprinter_mem.h +++ b/libc/include/sprinter_mem.h @@ -117,11 +117,12 @@ void bank_write(uint8_t phys_page, uint16_t off, const void *src, uint16_t n) int bank_load_file(uint8_t phys_page, uint16_t off, const char *path, uint16_t max_size); -/* То же, но из УЖЕ ОТКРЫТОГО файла и с текущей позиции (её ставит вызывающий - * через lseek). Нужна тем, кто читает из одного файла много кусков: `open` - * стоит дороже самого чтения (замер: 51,4 мс против 32,6 мс на 16 КБ), и - * держать fd открытым — единственный способ эту цену не платить. */ +/* Обмен с УЖЕ ОТКРЫТЫМ файлом и его текущей позицией (её ставит вызывающий + * через lseek). `open` стоит дороже самого чтения (замер: 51,4 мс против + * 32,6 мс на 16 КБ), а явное владение fd позволяет построить потоковый + * copy-job с собственной политикой create/temp/rename. */ int bank_read_page(int fd, uint8_t phys_page, uint16_t off, uint16_t size); +int bank_write_page(int fd, uint8_t phys_page, uint16_t off, uint16_t size); int bank_save_file(uint8_t phys_page, uint16_t off, const char *path, uint16_t size); diff --git a/libc/io/open.c b/libc/io/open.c index 838b1a6..0ec3ec6 100644 --- a/libc/io/open.c +++ b/libc/io/open.c @@ -27,16 +27,23 @@ static int _estex_open_raw(const char *path, int posix_mode) __naked (void)path; (void)posix_mode; __asm push ix - ;; HL = путь, DE = posix_mode. Перевести в нумерацию ESTEX. + ;; HL = путь, DE = flags. Низкие биты libc: W=1, R=2, RW=3; + ;; ESTEX: RW=0, R=1, W=2. flags=0 оставляем совместимым с R. ld a, e and a, #0x03 - ld c, #1 - or a, a - jr Z, _oopen_real - ld c, #2 - dec a - jr Z, _oopen_real ld c, #0 + or a, a + jr Z, _oopen_read + cp a, #1 + jr Z, _oopen_write + cp a, #2 + jr Z, _oopen_read + jr _oopen_real ; 3 = read/write + _oopen_read: + ld c, #1 + jr _oopen_real + _oopen_write: + ld c, #2 _oopen_real: ld a, c ld c, #0x11 ; ESTEX OPEN diff --git a/libc/mem/bank_write_page.c b/libc/mem/bank_write_page.c new file mode 100644 index 0000000..32751f0 --- /dev/null +++ b/libc/mem/bank_write_page.c @@ -0,0 +1,32 @@ +/* + * bank_write_page — записать диапазон EMM-страницы в УЖЕ ОТКРЫТЫЙ файл. + * + * Это симметричная операция к bank_read_page(): вызывающий владеет fd, + * позицией файла и политикой create/temp/rename, а функция только временно + * отображает физическую страницу в W3 и передаёт её диапазон ESTEX WRITE. + * W3 восстанавливается до возврата на всех путях. + * + * ABI: обычный SDCC __sdcccall(1). Возврат — записанный размер либо + * -1 с errno; допустимый диапазон страницы проверяется до переключения W3. + */ + +#include +#include +#include +#include + +int bank_write_page(int fd, uint8_t phys_page, uint16_t off, uint16_t size) +{ + uint8_t saved; + int n; + + if (off > 0x3FFFu || size > (uint16_t)(0x4000u - off)) { + errno = EINVAL; + return -1; + } + saved = _io_page_w3; + sprinter_page_w3(phys_page); + n = write(fd, (const void *)(0xC000u + off), size); + sprinter_page_w3(saved); + return n; +} diff --git a/tests/openenv/openenv.c b/tests/openenv/openenv.c index 7b4bfc9..7810ee0 100644 --- a/tests/openenv/openenv.c +++ b/tests/openenv/openenv.c @@ -63,6 +63,19 @@ int main(void) close(fd); printf("contents after trunc+write: \"%s\" (expect \"XYZ\")\n", buf); + /* Регрессия таблицы режимов open(): O_RDONLY=2 должен стать режимом + * ESTEX 1. До исправления 2026-09-08 он ошибочно превращался в RW=0. */ + errno = 0; + fd = open("TMP1.TXT", O_RDONLY); + if (fd >= 0) { + int wr = write(fd, "!", 1); + printf("write through O_RDONLY: rc=%d errno=%d (expect -1/%d)\n", + wr, errno, EROFS); + close(fd); + } else { + printf("open O_RDONLY for mode test failed: errno=%d\n", errno); + } + /* 3. O_APPEND: writes go to end. */ fd = open("TMP1.TXT", O_WRONLY | O_APPEND); if (fd >= 0) { diff --git a/toolchain/make_hdd.sh b/toolchain/make_hdd.sh index 8d704b5..a4aa885 100755 --- a/toolchain/make_hdd.sh +++ b/toolchain/make_hdd.sh @@ -1,5 +1,5 @@ #!/bin/bash -# make_hdd.sh [--dest DIR] OUT.chd FILE1 [FILE2 ...] +# make_hdd.sh [--dest DIR] OUT.chd FILE_OR_TREE1 [FILE_OR_TREE2 ...] # # Собирает 32 МБ RAW-образ жёсткого диска (MBR + один раздел FAT16) с # указанными файлами и конвертит его в CHD для MAME (монтируется как -hard2). @@ -15,6 +15,9 @@ # toolchain/make_hdd.sh out.chd prog.exe KID:kid0.atl GUARD:g0.atl ... # — аргумент вида КАТАЛОГ:файл кладёт файл в подкаталог относительно # DIR; без префикса файл идёт непосредственно в DIR. +# toolchain/make_hdd.sh out.chd prog.exe /tmp/test-tree +# — каталог копируется рекурсивно вместе со своим именем. Это заметно +# ускоряет сборку нагрузочных образов с сотнями мелких файлов. set -e DEST_DIR=${HDD_DEST_DIR:-} @@ -118,9 +121,9 @@ echo "--- копирование файлов ---" make_dir_tree "$DEST_DIR" DEST_ROOT="z:${DEST_DIR:+/$DEST_DIR}" -# Аргумент может быть либо файлом (кладём в корень), либо парой -# КАТАЛОГ:файл — тогда каталог создаётся относительно DEST_ROOT и файл -# кладётся туда. Имена на Sprinter/DSS — 8.3. +# Аргумент может быть файлом, деревом каталогов либо парой КАТАЛОГ:файл. +# Дерево копируется рекурсивно вместе с именем своего корня. Имена на +# Sprinter/DSS — 8.3. for f in "$@"; do case "$f" in *:*) dir="${f%%:*}"; src="${f#*:}" @@ -129,7 +132,11 @@ for f in "$@"; do # непрошедший вопрос mtools снова окажется невидимым. make_dir_tree "$target_dir" mt mcopy -o "$src" "z:/$target_dir/" ;; - *) mt mcopy -o "$f" "$DEST_ROOT/" ;; + *) if [ -d "$f" ]; then + mt mcopy -s -o "$f" "$DEST_ROOT/" + else + mt mcopy -o "$f" "$DEST_ROOT/" + fi ;; esac done