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

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

Добавить проектную документацию, HDD/MAME-сценарии и проверенные артефакты. Расширить libc операцией bank_write_page, исправлением режима O_RDONLY и связанными регрессионными проверками.
This commit is contained in:
2026-09-10 10:45:30 +03:00
parent 05bcd8197e
commit 8e389c03f8
870 changed files with 21310 additions and 25 deletions
+24
View File
@@ -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"
+24
View File
@@ -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"
+24
View File
@@ -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"
+24
View File
@@ -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"
+24
View File
@@ -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"
+24
View File
@@ -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"
+24
View File
@@ -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"
+24
View File
@@ -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"
+24
View File
@@ -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"
@@ -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"
+24
View File
@@ -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"
+18
View File
@@ -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"
+23
View File
@@ -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"
+23
View File
@@ -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"
+24
View File
@@ -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"
+24
View File
@@ -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"
+24
View File
@@ -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"
+25
View File
@@ -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"
+24
View File
@@ -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"
+24
View File
@@ -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"
+24
View File
@@ -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"
+24
View File
@@ -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"
+24
View File
@@ -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"
+21
View File
@@ -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"
+21
View File
@@ -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"
+20
View File
@@ -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"
+20
View File
@@ -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"
+20
View File
@@ -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"
+20
View File
@@ -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"
+20
View File
@@ -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"
+20
View File
@@ -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"
+20
View File
@@ -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"
@@ -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"
+20
View File
@@ -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"
+36
View File
@@ -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"
+30
View File
@@ -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"
+30
View File
@@ -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"
@@ -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)"
+53
View File
@@ -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)"
+26
View File
@@ -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)"
+31
View File
@@ -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)"
+25
View File
@@ -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"
+27
View File
@@ -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"
+26
View File
@@ -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"
+24
View File
@@ -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"
+38
View File
@@ -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"
+28
View File
@@ -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"
+25
View File
@@ -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"
+23
View File
@@ -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"
+23
View File
@@ -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"
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
+143
View File
@@ -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)
@@ -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)
+136
View File
@@ -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)
+75
View File
@@ -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()
@@ -0,0 +1,110 @@
/*
* p10_rename_probe.c — target-регрессия локального F6/RENAME.
*
* Проверяет файл, каталог, запрет overwrite и восстановление CWD через тот
* же низкоуровневый helper, который вызывает Commander.
*/
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#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;
}
+76
View File
@@ -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()
@@ -0,0 +1,85 @@
/* p11_delete_probe.c — target-регрессия однообъектного F8 delete. */
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
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;
}
+74
View File
@@ -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()
+58
View File
@@ -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()
+52
View File
@@ -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()
+67
View File
@@ -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()
+72
View File
@@ -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()
+49
View File
@@ -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()
+76
View File
@@ -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()
@@ -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()
@@ -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()
@@ -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("<I", raw, 446 + 8)[0]
self.part = part * 512
self.bps = struct.unpack_from("<H", raw, self.part + 11)[0]
self.spc = raw[self.part + 13]
reserved = struct.unpack_from("<H", raw, self.part + 14)[0]
fats = raw[self.part + 16]
self.root_entries = struct.unpack_from("<H", raw, self.part + 17)[0]
self.spf = struct.unpack_from("<H", raw, self.part + 22)[0]
self.fat = self.part + reserved * self.bps
self.root = self.fat + fats * self.spf * self.bps
root_sectors = (self.root_entries * 32 + self.bps - 1) // self.bps
self.data = self.root + root_sectors * self.bps
self.cluster_size = self.bps * self.spc
def cluster_offset(self, cluster: int) -> int:
return self.data + (cluster - 2) * self.cluster_size
def next_cluster(self, cluster: int) -> int:
return struct.unpack_from("<H", self.raw, self.fat + cluster * 2)[0]
def directory_bytes(self, cluster: int | None) -> 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("<H", entry, 26)[0]
def file_bytes(self, entry: bytes) -> bytes:
remaining = struct.unpack_from("<I", entry, 28)[0]
cluster = self.cluster(entry)
result = bytearray()
while remaining and 2 <= cluster < 0xFFF8:
offset = self.cluster_offset(cluster)
take = min(remaining, self.cluster_size)
result += self.raw[offset:offset + take]
remaining -= take
cluster = self.next_cluster(cluster)
return bytes(result)
def metadata(entry: bytes) -> tuple[int, int, int]:
return (
entry[11],
struct.unpack_from("<H", entry, 24)[0],
struct.unpack_from("<H", entry, 22)[0],
)
def records(raw_path: pathlib.Path) -> 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()
@@ -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()
@@ -0,0 +1,121 @@
/*
* p19_overwrite_probe.c — target-проверка backup/commit/rollback copy-job.
*
* UI-policy проверяется отдельно. Здесь ошибки подаются точно между
* последним write и commit, чтобы доказать сохранность старого target.
*/
#include <stdint.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <conio.h>
#include <sprinter_mem.h>
#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;
}
@@ -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()
@@ -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

Some files were not shown because too many files have changed in this diff Show More