# Автотестирование в MAME Единый справочник: как запускать программы Sprinter в эмуляторе MAME **без участия человека**, вводить команды, снимать скриншоты, завершать сессию и анализировать результат. Если нужно что-то про автотесты в MAME — смотреть сюда. Весь механизм собран в одном инструменте: **`toolchain/mame_interactive.py`**. --- ## 1. TL;DR ```bash # собрать .exe (пример) make -C tests/bgitest # запустить в MAME, снять экран, выйти по таймауту python3 toolchain/mame_interactive.py tests/bgitest/bgitest.exe \ --snap 12,14 --timeout 16 ``` Инструмент сам: 1. проверит, что нет висящих копий MAME; 2. положит `.exe` на дискету A: (`mame/v306/IMG/mc.img`); 3. запустит MAME с драйвером `sprinter`; 4. дождётся приглашения `C:\>` и **напечатает `a:\bgitest.exe` + Enter** (эмулируя нажатия клавиш); 5. снимет скриншоты в указанные секунды эмулированного времени; 6. завершит сессию по таймауту; 7. выведет пути к PNG-скриншотам. Скриншоты лежат в `mame/v306/snap_auto/sprinter/` (`0000.png`, `0001.png`, …). Их читает Claude визуально (инструментом Read) — текст с экрана программно НЕ распознаётся. --- ## 2. Инструмент: `mame_interactive.py` ``` python3 toolchain/mame_interactive.py [exe] [--data f ...] \ [--launch-at T] [--step "T:TEXT" ...] [--snap t1,t2,...] [--timeout N] ``` | Аргумент | Назначение | |----------|-----------| | `exe` | `.exe` кладётся на A: и **авто-запускается** (печатается `a:\`+Enter в момент `--launch-at`). Без `exe` работаем на голой командной строке. | | `--data f ...` | доп. файлы на дискету A: (данные для теста). | | `--launch-at T` | секунда, когда печатается запуск `exe` (по умолчанию **8**). | | `--step "T:TEXT"` | в момент `T` сек напечатать `TEXT`. Можно много раз — диалог с уже запущенной программой. В `TEXT`: `\n`=Enter, `\t`=Tab; заглавные и символы через Shift — автоматически. | | `--snap t1,t2,...` | секунды эмуляции для скриншотов. По умолчанию: `launch_at+4` и `+6` (для `exe`), либо сразу после последнего ввода. | | `--timeout N` | секунд эмуляции до принудительного выхода. По умолчанию — чуть позже последнего скриншота. | **Важно:** все времена — это **секунды эмулированного времени от старта машины** (не от нажатий, их «нет»). Загрузка DSS до `C:\>` занимает ~7 секунд, поэтому `--launch-at 8` и скриншоты с ~12 с. ### Типовые рецепты ```bash # 1. Запустить тест и снять результат (самый частый случай) python3 toolchain/mame_interactive.py tests/rt_test/rt_test.exe \ --snap 12,14 --timeout 16 # 2. Набрать команду на голой командной строке (без exe) python3 toolchain/mame_interactive.py --step "8:dir\n" \ --snap 10,11 --timeout 12 # 3. Запустить программу и ответить на её ввод (например, выбор пункта меню) python3 toolchain/mame_interactive.py tests/menu/menu.exe \ --step "13:2\n" --snap 15 --timeout 17 # 4. Тест с файлом-данными на дискете python3 toolchain/mame_interactive.py examples/mdview2/mdview2.exe \ --data doc.md --step "13:mdview2 doc.md\n" --snap 16 --timeout 18 ``` --- ## 3. Как это работает внутри ### 3.1 Запуск MAME Нативный `mame.arm` (arm64) в `mame/v306/`, драйвер `sprinter`, BIOS v3.06. Полный набор аргументов зашит в `COMMON_ARGS` внутри скрипта: две дискеты (A: наш `mc.img`, B: DSS 1.71u), два HDD-образа (система + медиа), CD-ROM, ZX-Bus карта NeoGS, `-video opengl -window`, `-snapshot_directory`, `-autoboot_script <сгенерированный .lua>`. ### 3.2 Ввод с клавиатуры — ключевой момент У Sprinter в MAME **две** клавиатуры: - `IO_LINE0..7` — легаси ZX-Spectrum-матрица (порт `0xFE`). DSS её для командной строки **НЕ читает**. - `root:kbd:ms_naturl` — **настоящая AT/PS-2 клавиатура**, подключённая последовательно к SIO Z84C015 (`sprinter.cpp:2037`). Именно её DSS читает как поток scancode'ов. Поэтому **не работают** (проверено многократно): `natkeyboard:post`, `-autoboot_command`, а также `set_value` по полям `:IO_LINE*`. Всё это бьёт в ZX-матрицу, которую DSS игнорирует. **Работает** — прямое управление полями AT-клавиатуры из Lua: ```lua manager.machine.ioport.ports[":kbd:ms_naturl:P1.4"].fields["D"]:set_value(1) -- нажать ... подождать ~0.06с ... manager.machine.ioport.ports[":kbd:ms_naturl:P1.4"].fields["D"]:set_value(0) -- отпустить ``` `at_keyboard` сам сгенерит make/break scancode'ы → SIO → DSS. Скрипт хранит раскладку `char → (порт, битовая маска)` (словарь `PHYS` + `SHIFTED` для Shift-символов) и разворачивает строку в список timed-событий `(время, порт, маска, значение)`. Backslash `\` в AT-клавиатуре есть (поле `P2.1`/0x4) — путь `a:\name.exe` вводится полностью. ### 3.3 Тайминг (Lua) Генерируется `_interactive_gen.lua`, который через `emu.register_periodic` на каждом кадре сверяет **эмулированное время** и проигрывает события ввода, снимает скриншоты и завершает сессию. Время берётся как `t.seconds + t.attoseconds/1e18`, потому что `attotime.seconds` — **целое** (дробную часть отбрасывает); если считать по нему, все события схлопнутся в 1-секундную сетку. Старт отсчёта — `emu.add_machine_reset_notifier` (НЕ `emu.register_start` — он deprecated). ### 3.4 Скриншоты `manager.machine.video:snapshot()` пишет PNG в каталог из `-snapshot_directory` (`mame/v306/snap_auto/sprinter/`). Скрипт перед запуском чистит этот каталог, потом печатает пути к готовым файлам. Claude открывает их инструментом Read (визуальный анализ). ### 3.5 Завершение сессии Два рубежа, чтобы MAME гарантированно не «завис»: - в Lua: при `elapsed >= timeout` → `manager.machine:exit()` (чистый выход); - снаружи: shell-обёртка `timeout ./mame.arm …` как страховка. --- ## 4. Предпосылки (окружение) - **MAME**: `mame/v306/mame.arm` + образы в `mame/v306/IMG/` (`mc.img` — наш перезаписываемый A:, `dss171u.img`, HDD `.chd`, CD `.iso`). Каталог `mame/` целиком в `.gitignore` — поэтому инструмент живёт в `toolchain/`, а не рядом с MAME. - **Загрузка должна доходить до `C:\>`.** `system.bat` на системном диске НЕ должен автоматически запускать Flex Navigator или приложение — иначе мы не попадём на командную строку и ввод уйдёт в чужую программу. (Это файл на HDD-образе, вне репозитория; правится один раз.) - **Нет висящих копий MAME.** Несколько одновременных инстансов пишут в один `mc.img` и дают недостоверный результат. Скрипт проверяет это сам (`pgrep`), но при ручных запусках MAME — проверяйте `ps aux | grep mame`. --- ## 5. Как выбирать времена - **Загрузка до `C:\>`:** ~7 секунд → `--launch-at 8` безопасно. - **Набор пути `a:\name.exe`:** ~13 символов × 0.14с ≈ 1.8с → команда уходит около 9.8с, программа стартует ~10с. - **Скриншот:** давайте программе дорисоваться. Быстрая программа — снимать с ~12с; если рисует долго/по частям, снимайте несколько кадров (`--snap 12,16,20`) и смотрите, где картинка «дособралась». - **Диалог с программой (`--step`):** времена шагов ставьте ПОСЛЕ старта программы (например, запуск на 8с, ответ на ввод на 13–15с). --- ## 6. Анализ результата - Скриншоты — **единственный** способ проверки: программного чтения текстового/графического VRAM нет, OCR нет. Claude читает PNG глазами (инструмент Read с картинкой). - Лог MAME фильтруется по строкам `[interactive]` (моменты снимков и выхода) — видно, в какие секунды сделаны кадры. - Если картинка «не дособралась» — снять более поздний кадр (увеличить `--snap`/`--timeout`). --- ## 7. Раскладка клавиатуры (справочно) Раскладка снята дампом ioport-полей `:kbd:ms_naturl:*` живой машины. Она зашита в `PHYS`/`SHIFTED` внутри `mame_interactive.py`. Поддержаны: буквы (a–z, A–Z через Shift), цифры, пробел, Enter (`\n`), Tab (`\t`), и символы ``- = [ ] \ ; ' , . / ` `` плюс их Shift-версии `! @ # $ % ^ & * ( ) _ + { } | : " < > ? ~`. Если понадобится клавиша вне списка — снять её поле дампом (пример Lua-пробы ниже) и добавить в `PHYS`: ```lua -- дамп всех полей клавиатуры в лог for tag, port in pairs(manager.machine.ioport.ports) do if tostring(tag):find("kbd") then for fname, field in pairs(port.fields) do print(string.format("%s mask=0x%x %q", tag, field.mask, fname)) end end end ``` --- ## 8. Квирки и грабли (все, на которые уже наступали) - **`attotime.seconds` — целое.** Субсекундный тайминг только через `seconds + attoseconds/1e18`. - **Автоповтор (typematic).** Клавишу держать коротко (~0.06с). Если держать ~1с — `d` превратится в `dddddd`. - **Слипание scancode'ов.** Между символами ~0.14с. - **Не та клавиатура.** Ввод — только в `:kbd:ms_naturl`, НЕ в `:IO_LINE*`, НЕ через `natkeyboard`/`-autoboot_command`. - **Загрузка мимо `C:\>`.** Если `system.bat` что-то автозапускает — ввод уходит в чужую программу; вернуть чистую командную строку. - **Висящие копии MAME.** Всегда проверять перед запуском. - **macOS-специфика (справочно):** известный баг MAME (mamedev/mame#10612 — потеря ввода в fullscreen при движении мыши на старте) к нам НЕ относится: работаем в `-window`, ввод скриптовый. --- ## 9. На будущее (заметки, ещё не в инструменте) - **Быстрый накопитель.** Тестам, которым важна скорость диска (напр. потоковое чтение), имеет смысл копировать файлы с медленной дискеты A: на HDD `C:\TEMP` перед запуском. - **Видео+звук.** MAME умеет писать AVI (`-aviwrite`) — для тестов с анимацией/звуком, где скриншотов мало. Пока не подключено к скрипту. - **Ручная отладка ввода.** Запуск MAME с `-console` даёт интерактивный Lua-REPL — удобно нащупывать поля/тайминги вживую перед скриптованием. - **Второй видеорежим/варианты BIOS** — при необходимости менять `COMMON_ARGS`. ```