Содержит правки из следующих коммитов: - WIP: all-in-one - Janko: cache on - DeZog fix - Revert "drop mlz" - emu/debug/debugcpu.cpp,sinclair/spectrum.cpp: Guarded pointer accessors - sinclair/sprinter.cpp tmkonf - dma delay under investigation - harddisks-shareable.diff - ignore - плагин и скрипты для управления MAME by Claude code (MCP) - много всего - mouse release
33 KiB
Управление эмулятором MAME из Claude — переносимый онбординг
Назначение этого файла. Подгрузи его в новую сессию Claude (в любом проекте), и я сразу умею управлять эмулятором MAME (на примере машины Sprinter, CPU z84c015): читать/писать регистры и память (с учётом банкирования), ставить брейк-/вотчпойнты, шагать, дизассемблировать, видеть экран и управлять машиной (клавиатура, мышь, джойстик).
Как подгрузить: либо «прочитай файл
/Users/tolik/Documents/GitHub/mame/MAME_MCP_GUIDE.mdи действуй по нему», либо скопируй этот файл вCLAUDE.mdнового проекта.Все пути абсолютные — файл самодостаточен и не зависит от текущего проекта.
0. TL;DR — как поднять связку за 3 шага
# 1) Запустить MAME с дебаггером и плагином-мостом (см. полный конфиг в Debug.sh):
cd /Users/tolik/Documents/MAME && ./Debug.sh # содержит -debug -debugger auto (на macOS = Cocoa)
# но в Debug.sh нужно дописать в строку запуска: -plugin mamebridge
# (без него мост не поднимется). Полный минимум, если запускать вручную:
# ./mame sprinter -mouse -kbd ms_naturl,bios=sp2k -bios dev -debug -debugger auto -plugin mamebridge <диски...>
# Мост debugger-agnostic: одинаково работает с -debugger qt | imgui | auto(Cocoa).
# Проверено вживую под Cocoa (2026-05-22): все команды + цикл stop/step без таймаутов.
# 2) Зарегистрировать MCP-сервер в Claude Code (один раз на проект):
claude mcp add mame-z80 -- python /Users/tolik/Documents/GitHub/mame/src/mame_mcp.py
# 3) Готово. В сессии доступны MCP-инструменты read_registers, read_memory, step, scrpix и т.д.
Если MAME не находит плагин: добавь явно -pluginspath /Users/tolik/Documents/GitHub/mame/plugins.
Остановка MAME: НИКОГДА не kill/pkill. Слать debugger-команду exit
(через мост: debugger_command("exit") или просто положить запрос cmd exit).
Ответа не будет — процесс завершится, это нормально.
1. Архитектура связки
Claude (MCP-клиент)
│ вызывает MCP-инструменты
▼
mame_mcp.py (FastMCP-сервер, stdio) pip install "mcp[cli]"
│ файловый IPC: пишет req_<id>.txt, ждёт resp_<id>.txt в общей папке
▼
plugins/mamebridge/init.lua (плагин MAME)
│ читает запрос, исполняет в дебаггере MAME, атомарно пишет ответ
▼
MAME debugger ──> эмулируемая машина (Sprinter / любой CPU)
- IPC: текстовые файлы
req_<id>.txt/resp_<id>.txtв общей папке (по умолчанию/tmp/mame_mcp). Запись атомарная (.tmp+ rename). - Почему плагин, а не
-autoboot_script: старыйmame_bridge.luaопрашивал IPC черезemu.add_machine_frame_notifier, который молчит, пока машина hard-stopped в дебаггере → интерактивный stop→step→inspect зависал. Плагин качает опрос черезemu.register_periodic(), который вызывается изemulator_info::periodic_check()ВНУТРИ цикла дебаггераwhile (is_stopped())(src/emu/debug/debugcpu.cpp). → шаги/инспекция в стопе работают. - Совместимость: протокол IPC у
mame_bridge.lua(fallback) и плагина идентичен —mame_mcp.pyодин и тот же.
Файлы (всё в /Users/tolik/Documents/GitHub/mame)
| Файл | Роль |
|---|---|
plugins/mamebridge/init.lua |
плагин-мост (вся логика команд, ввод, scrpix) |
plugins/mamebridge/plugin.json |
манифест, "name":"mamebridge", "start":"false" |
src/mame_mcp.py |
FastMCP-сервер, объявляет MCP-инструменты |
src/mame_bridge.lua |
старый autoboot-вариант, fallback для live-инспекции |
src/CLAUDE.md |
подробная проектная документация (источник истины) |
Ключевое про окружение
plugins/— это симлинк из активногоpluginspath:/Users/tolik/Documents/MAME/plugins → /Users/tolik/Documents/GitHub/mame/plugins. → правки файлов плагина в репозитории сразу живые (Lua интерпретируется), пересборка MAME НЕ нужна.- Запускать только ОДИН
mameпротив однойMAME_MCP_DIR. Второй инстанс не стартует, а застрявший первый продолжит отвечать СТАРЫМ кодом плагина — путаница. - Запускать с реальным конфигом (
-bios dev+ диски), см.~/Documents/MAME/Debug.sh. Без-bios devдефолтный BIOSsp2kотсутствует и машина не стартует. - Плагин
portnameкогда-то ронял загрузку (register_startудалён из API). Если сноваattempt to call a nil valueна старте — это он; запусти с-noplugin portnameлибо проверь, что он используетadd_machine_reset_notifier.
Переменные окружения (должны совпадать на обеих сторонах)
| Переменная | Что | Дефолт |
|---|---|---|
MAME_MCP_DIR |
папка IPC | /tmp/mame_mcp |
MAME_MCP_CPU |
тег CPU | :maincpu |
MAME_MCP_SNAP_DIR |
папка скриншотов | /tmp/mame_snap (чистится при ребуте) |
MAME_MCP_TIMEOUT |
таймаут ожидания ответа (сторона Python) | 10 сек |
2. MCP-инструменты (что я вызываю)
Адреса принимают "0xC000", голый hex "C000" или десятичное "49152"
(нормализует parse_addr).
CPU / память
read_registers()— регистры Z80/8080 (PC, SP, AF, BC, DE, HL, IX, IY, ...).read_memory(address, length=16)— СЫРОЕ программное пространство (без банков). На Sprinter окна Z80 живут в program 0x10000+ — для логики используй lmem.read_logical_memory(address, length=16)— как видит Z80 через банки (логический 0x0000–0xFFFF → program0x10000|addr). Обычно нужен этот.read_vram(address, length=16)— напрямую из видеоОЗУ (share:vram, 256K), минуя банкинг. Для пиксельных данных, тайлов/дескрипторов, палитры.read_share(name, address, length=16)— из именованного share (vram,fastram, ...). См.list_shares().list_shares()— список share/region (теги + размеры).write_memory(address, hex_bytes)— запись в program space ("3E01CD0500"). Для логического вида Z80 пиши по0x10000|addr.
Точки останова / шаги
set_breakpoint(address, condition="")— PC-брейк; condition — выражение дебаггера ("A==0"). Возвращает индекс.clear_breakpoint(index),list_breakpoints().set_watchpoint(address, length, access="w", space="program")—r/w/rw;space=program(умолч.)|data|io|opcodes(напр.space="io"ловит Z80 IN/OUT по порту). Bridge-verb:wp ADDR LEN ACCESS [space]. Ставится нативно (cpu().debug:wpset); адрес ЛИТЕРАЛЬНЫЙ в пространстве — для Z80-окна в program давай 0x10000+ (напр. 0x18000 = окно 2).clear_watchpoint(index).step(count=1),step_over(count=1)(CALL до конца),step_out().resume()(cont),pause(),status()(running/stopped + PC).disassemble(address, num_bytes=32).
Экран
scrpix(x,y,w,h)— ⭐ ОСНОВНОЙ способ «смотреть»: читает ОТРИСОВАННЫЕ пиксели черезscreen:pixel(x,y), по 4 hex-символа на пиксель (пен), row-major, до 8192 px. Это видеовыход, декодированный железом → правильные экранные коорд., без ручной возни с тайлами/4bpp/буфером.screenshot(name="")— PNG активного экрана, возвращает путь. Последнее средство (дорого по токенам, коорд. «на глаз»). Снимок = последний отрисованный кадр; в hard-stop сначала ненадолгоresume()для свежего кадра.
Ввод (клавиатура/мышь/джойстик) — машина должна быть RUNNING (resume перед вводом)
list_ports()— порты и поля (тег, маска, имя).press_key(key, frames=3)— нажать ИМЕНОВАННУЮ клавишу на N кадров с авто-отпусканием. Бьёт по ОБЕИМ клавиатурам (PC + ZX), где есть эквивалент.type_text(text, coded=False)— печать через natkeyboard; еслиcoded, понимает{ENTER}и пр. (но НЕ курсорные стрелки). Для UI прошивки лучше press_key.move_mouse(dx, dy, frames=3)— двигать ОБЕ мыши (Kempston + RS232 COM), относительные оси накапливаются за кадры.click_mouse(button="left", frames=3)— клик (left/right/middle) на обеих мышах.press_input(port, mask, frames=2)— низкоуровневый цифровой ввод (press_input(':JOY1','0x400')).set_input(port, mask, value, frames=0)— задать поле (analog/digital).debugger_command(raw)— ⭐ escape-hatch: любая команда консоли дебаггера ("print pc","print (ib@C9)","trace t.log","exit").
3. Чтение портов эмулируемой машины
Чтобы узнать значение, которое выдаёт порт: в консоли дебаггера
print (ib@C9) (читает io-байт порта 0xC9, напр. rgmod). Через мост:
debugger_command("print (ib@C9)"). ib@<addr> = io-space byte по адресу.
Не путать внешние и внутренние порты Sprinter:
- Внешние — порты, с которыми Z80 работает через OUT/IN.
- Внутренние — порты конфигурации Altera FPGA, маппятся на внешние через таблицу DCP (decoded control ports).
Активный двойной буфер экрана выбирается портом rgmod.
4. Sprinter: карта памяти и видео (проверено вживую по sinclair/sprinter.cpp)
CPU z84c015, ТРИ пространства: program (mem), io, opcodes (fetch). Память банкируемая — читать с осторожностью (поэтому путь B/Lua-мост, а не gdbstub: gdbstub видит плоское адресное пространство и не следит за банками).
Банкинг — 64K логических Z80 → физические страницы:
- 4 окна по 16K. Program-пространство ШИРЕ 64K: окна живут в program 0x10000+
(
map_mem, sprinter.cpp:1448): win0 Z80 0x0000–0x3FFF → program 0x10000; win1 0x4000→0x14000; win2 0x8000→0x18000; win3 0xC000→0x1C000. mem L(сырой program 0..0xFFFF) идёт вbootstrap_r(sprinter.cpp:1185), который форвардит в0x10000|Lпосле загрузки FPGA-битстрима, а пока грузится — отдаёт fastram (ненадёжно на раннем этапе). Используйlmem(читает0x10000|L) для реального вида Z80. Проверено: lmem == mem(0x10000|L) == dasm.- Физ. страницы —
m_ram(по умолчанию 64M), индексpage<<14(configure_entries, sprinter.cpp:1577). Страница окна считается вupdate_memory(sprinter.cpp:327) из кучи портов (m_pn/7FFD, m_sc/1FFD, m_cnf, m_dos, m_rom_rg, ...) через DCP-таблицуm_ram_pages. Еслиpage&0xF0==0x50— это апертура VRAM:phys = (0x50<<14)+m_port_y*1024+(offset&0x3FF).
Видео (VRAM = 256K share :vram, читать через read_vram):
- Выбор режима:
m_conf(Game Config) → screen_update_game, иначе screen_update_graph (sprinter.cpp:404). Режимы: 320x256x256, 640x256x16, Spectrum, text. - Per-16x8-тайл 4-байтный «mode descriptor»:
as_mode(a,b)=vram+(1+a*2+0x80*(m_rgmod&1))*1024+0x300+b*4(sprinter.cpp:554). - Пиксель (draw_tile, sprinter.cpp:453):
color = vram[(y+((dy-hold.y)&7>>lowres))*1024 + x + ((dx-hold.x)&15>>(1+lowres))],x=(mode[0][0:4]<<6)|(mode[1][0:3]<<3),y=mode[1][3:8]<<3. 8bpp если mode[0] bit5, иначе 4bpp (2 px/байт). База палитры= mode[0][6:8]<<8. - Палитра — в VRAM: запись по
laddr>=0x3E0ставит пен(offset[2:5]*256 + offset>>10)= RGB-тройка поoffset&~3(vram_w sprinter.cpp:1287). 256 пенов × 8 палитр. Текстовые пены0x400+.
Брейк-/вотчпойнты по регионам:
- PC-брейки берут логический 0..0xFFFF (fetch-пространство) — независимо от банка.
- Вотчпойнты по умолчанию в program space:
wpset ADDR,LEN,rw. Для конкретного окна Z80 — адрес 0x10000+. - Поймать ПЕРЕКЛЮЧЕНИЕ БАНКОВ — io-вотчпойнт на порты пейджинга. В текущем MAME
команда ЗАВИСИТ ОТ ПРОСТРАНСТВА:
wpisetдля io (старая формаwpset ADDR,1,w,1,1,iотвергается — "too many parameters"). Напр.debugger_command("wpiset 0xc1,1,w")(порт 0xC1=m_pn) или 0xC0 (m_sc), 0xC4 (m_port_y), 0xC5 (m_rgmod), 0xC6 (m_cnf). (wpset=program,wpdset=data,wpiset=io.) ВАЖНО: порты, которые пишутся внутренне через FPGA/DCP (напр. rgmod 0xC9), io-вотчпойнт НЕ ловит — туда нет Z80-команды OUT; io-wp ловит только реальные IN/OUT по этому порту. m_pages/m_pn/...— приватные C++ члены, в Lua НЕ проброшены. Чтобы понять текущий банкинг — следи за портами или читай копии DCP в m_ram.
5. Ввод в Sprinter (проверено вживую)
Ввод обрабатывается только пока машина RUNNING — frame-нотифаеры и таймер
natkeyboard не тикают в hard-stop. Значит resume ПЕРЕД вводом (и pause после,
если надо). Плагин держит каждое нажатие N кадров, переустанавливая set_value(1)
каждый кадр (MAME перечитывает поля покадрово), и сам отпускает по номеру кадра.
Всё в ДВУХ экземплярах. На реальном хосте одно нажатие идёт в ОБЕ клавиатуры,
движение — в ОБЕ мыши. Поэтому press_key/move_mouse/click_mouse бьют по обеим:
- Клавиатуры: PC PS/2
:kbd:ms_naturl:P1.0..P2.7(его читает Flex Navigator и большинство прошивок — проверено: курсор/Tab двигают UI) И ZX-матрица:IO_LINE0..7.press_keyмаппит имена в обе, где есть эквивалент. - Мыши: Kempston
:mouse_input1/2(X/Y маска 0xFF) + кнопки:mouse_input3, И RS232 COM:rs232:microsoft_mouse:X/Y(маска 0xFFF) +:BTN. В Flex Navigator читается COM-мышь, а не Kempston. Оси относительные → движение держим несколько кадров для накопления. - Джойстики:
:JOY1/:JOY2(A=0x400, B=0x010, Up=0x208, Down=0x104, Left=0x002, Right=0x001) черезpress_input.
Имена клавиш в press_key: a–z, 0–9, enter, space, esc, tab, backspace,
up/down/left/right, home, end, pgup, pgdn, ins, del, f1–f12, shift, ctrl, alt,
symbolshift и символы ; ' / . , - = [ ] \ `.
ВАЖНО про скорость и «залипание» (объяснение пользователя):
«Мышка и клавиши летят в буферы SIO процессора на скорости 1200 бод, поэтому зажатие клавиши на несколько кадров может сгенерировать много повторов.»
PS/2 typematic delay ≈ 60 опросов @60Гц ≈ 1 сек. Поэтому:
- для ОДИНОЧНОГО нажатия держи мало кадров (frames=2..3) — иначе авто-повтор «настрочит» символ десятки раз и список/курсор «улетит»;
- между одиночными нажатиями есть естественная пауза из-за 1200 бод — это нормально, «быстрее» = риск повторов.
ZX-токены: в TR-DOS работают токены клавиатуры как в BASIC спектрума, поэтому
LIST — это ОДНА клавиша K (IO_LINE6, маска 0x04).
type_text использует natkeyboard, но целится в ОДНУ клавиатуру и требует in_use;
для UI прошивки предпочитай press_key. natkeyboard НЕ умеет курсорные стрелки.
Перед natkeyboard выбери целевую клавиатуру (в плагине — команда kbsel ms_naturl).
list_ports — чтобы заново найти теги/маски.
6. Как «смотреть» на экран — три метода и что выбирать
Оценка по скорости / точности / токенам:
| Метод | Скорость | Точность коорд. | Токены | Когда |
|---|---|---|---|---|
scrpix (screen:pixel) |
средняя | пиксель-точно (декодировано железом) | дёшево (hex обрабатывать в python-скрипте, в контекст — только вывод) | ⭐ по умолчанию для наблюдения экрана и наведения |
raw VRAM (read_vram) |
средняя | требует ручного декодера (тайлы/4bpp/буфер) — ненадёжно | дёшево | только для того, чего нет в отрисовке: back-buffer, палитра, дескрипторы тайлов |
| screenshot (PNG) | быстрее всего для всего экрана | «на глаз» | дороже всего (картинка целиком в контекст) | последнее средство, быстрый визуальный гештальт |
Правило (предпочтение пользователя): МИНИМИЗИРОВАТЬ скриншоты. По умолчанию — scrpix; raw VRAM — для «закулисных» данных; скриншот — только если иначе никак.
Двойная буферизация (важно при чтении VRAM напрямую): в видеопамяти ДВА
экрана — один отображается, второй в это время прорисовывается; после готовности
буферы переключаются. Активный набор дескрипторов выбирается портом rgmod
(print (ib@C9)). Список UI рисуется в АКТИВНОМ буфере (+0x20000), а КУРСОР мыши —
в ДРУГОМ буфере. Рекомендация: ставить эмулятор на pause и дампить именно
отображаемый экран (по rgmod). Из-за этого raw-VRAM-диффы давали ложные коорд.
курсора → для курсора предпочитай scrpix.
Поиск курсора сложен: текст И курсор оба белые (пен 0xFFFF=65535), цветом не
изолировать — только ДИФФОМ (чуть двинуть мышь, сравнить два scrpix-грэба;
изменившиеся пиксели = курсор). Точка клика = крайний левый верхний пиксель
курсора.
НЕ РЕШЕНО надёжно: замкнутое наведение мыши на конкретный мелкий пункт.
Дифф-поиск курсора флакки (пробное движение + тайминги/буфер теряют курсор),
масштаб мышь→пиксель плывёт. Надёжный путь — навигация клавиатурой (точные
одиночные press_key). Если мышь macOS не в зоне окна MAME — координаты могут
«замораживаться».
7. Что уже отлажено и работает
- ✅ Конвертация в плагин; брейки/шаги/инспекция РАБОТАЮТ в hard-stop
(
register_periodic). - ✅ Чтение банкируемой памяти (
lmem), VRAM/палитры, портов (print (ib@)). - ✅ Полное управление клавиатурой с точными одиночными нажатиями. Тест 1 пройден:
навигация в C:\ZX, запуск sprinter.zx → меню ZX → TR-DOS → выполнен
LIST. - ✅ Защитное отпускание ввода на
pause(release_all) и авто-отпускание по номеру кадра (tick_held в register_periodic) — иначе typematic-шторм. - ⚠️ Частично/НЕ решено: надёжное замкнутое наведение мыши на мелкий пункт через VRAM/scrpix (см. §6). Клавиатура — рабочий обходной путь.
Структура списка в Flex Navigator (C:\ZX): строки текста с шагом 8px; левая панель — колонки ~x48–150 (col1) и ~x166–220 (col2 с pent_*/sprinter); sprinter.zx ≈ строка 6 колонки 2. (Это эвристика из наблюдений, не «читы из RAM».)
8. Типовые рецепты
Старт отладочной сессии:
status() # running/stopped + PC
set_breakpoint("0x8000") # PC-брейк (логический адрес)
resume()
# ...брейк сработал, машина в стопе...
status() # state=stop, БЕЗ таймаута (это и есть фикс плагина)
read_registers(); step(); read_registers() # PC двигается, оставаясь в стопе
Проверка банкирования: прочитать байт из банкируемого региона → переключить
банк (программой или write_memory в порт пейджинга) → снова прочитать тот же
адрес: значение должно измениться (чтение идёт через живое адресное пространство).
Посмотреть кусок экрана дёшево: scrpix(x,y,w,h) → получить hex-пены →
обработать в python (искать пятно/край/дифф) → в контекст вернуть только вывод.
Корректно остановить MAME: debugger_command("exit") (НЕ kill).
9. Источник истины и расширение
- Полная проектная документация:
/Users/tolik/Documents/GitHub/mame/src/CLAUDE.md. - Логика моста (можно править — изменения живые из-за симлинка):
/Users/tolik/Documents/GitHub/mame/plugins/mamebridge/init.lua. - Объявления MCP-инструментов:
/Users/tolik/Documents/GitHub/mame/src/mame_mcp.py. - Эталон стиля плагина MAME:
/Users/tolik/Documents/GitHub/mame/plugins/gdbstub/.
10. Авторитетный справочник API и команд (из docs.mamedev.org)
Сверено с официальной докой: plugins, luascript (ref-common/core/devices/mem/
input/debugger), debugger (general/execution/watchpoint…). Здесь — то, что
неочевидно или чем стоит пользоваться вместо строк cmd.
10.1 Lua-API дебаггера (нативный — чище, чем cmd "...")
machine.debugger (manager.machine.debugger; nil если без -debug):
.execution_state(rw) —"run"/"stop"..visible_cpu(rw)..consolelog[],.errorlog[](ro) — строки вывода консоли/ошибок (можно ЧИТАТЬ результат команд, а не только слать их!).:command(str)— выполнить консольную команду дебаггера.
device.debug (напр. manager.machine.devices[":maincpu"].debug):
:bpset(addr [,cond] [,act])→ номер bp;:bpclear([n]),:bpenable([n]),:bpdisable([n]),:bplist()→ таблица (поля bp:.index .enabled .address .condition .action).:wpset(space, type, addr, len [,cond] [,act])— type"r"|"w"|"rw",space— объект адресного пространства (см. 10.4);:wpclear([n]),:wplist(space)(поля wp:.index .enabled .type .address .length .condition .action).:step([cnt]),:go()(нативныхover/out/disassemble НЕТ — они остаются наcmd).bpset/wpsetв биндинге требуютcondиactкакchar*→ передавай""если не нужны.
ПРИМЕНЕНО В МОСТЕ:
bp/bpclr/bplist/wp/wpclr/stepидут через нативныйcpu().debug:*(структурные возвраты: числовой индекс, таблицы; без скрейпинга consolelog).over/out/dasm— по-прежнемуrun_cmd(нет нативных). ВАЖНО: нативныйwpsetберёт адрес ЛИТЕРАЛЬНО в выбранном пространстве (без трансляции банков) — для Z80-окна в program давай 0x10000+ (напр. 0x18000 = окно 2), как вwrite_memory/mem. (Старый консольныйwpset 0x8000неожиданно релоцировал в 0x18000; нативный — литеральный и предсказуемый.)
10.2 Команды дебаггера — авторитетный синтаксис
- Watchpoints:
wp[{d|i|o}][set] <addr>[:<space>],<len>,<type>[,<cond>[,<act>]].wpset=program,wpdset=data,wpiset=io,wposet=opcodes. Можно и через суффикс пространства:wpset 0xC9:io,1,w. Переменные в cond/act:wpaddr,wpdata. Ещё:wpclear/wpdisable/wpenable [n,…],wplist [cpu]. - Breakpoints:
bpset <addr>[,<cond>[,<act>]],bpclear/bpdisable/bpenable [n,…],bplist. - Шаги/выполнение:
s[tep] [n],o[ver] [n],out,g[o] [addr],gv[blank],gi[nt] [irqline],gt[ime] <ms>,ge[x] [exc,[cond]],gbt [cond]/gbf [cond](взятая/невзятая ветвь),n[ext](до смены CPU),focus/ignore/observe <cpu>,trace {file|off}[,cpu[,…]],traceover,traceflush. - Общие:
print <expr>[,…](hex),printf <fmt>[,arg…](%d %x %X %o %c %s %%),logerror,history [cpu[,len]],softreset,hardreset,help [topic].
10.3 Выражения и операторы доступа к памяти
Форма: [prefix]<size>[@|!]<addr>.
- size:
b=8,w=16,d=32,q=64. @= ПОДАВИТЬ side-effects (безопасное чтение),!= НЕ подавлять.- prefix пространства:
p/lp=program(лог.),d/ld=data,i/li=io,3/l3=opcodes; физические —pp pd pi p3;r=прямой указатель program,o=прямой opcodes,m=region,s=share. - Примеры:
b@1234(program byte),ib@C9(io byte, как мы читаем rgmod),dw@300(data word),pp b@addr(физ. program byte). - Встроенные функции:
min/max(a,b),if(cond,t,f),abs(x),bit(x,n[,w]),s8/s16/s32(x)(знаковое расширение). - Адресация устройства/пространства:
[tag][:space]илиcpunum[:space](напр.maincpu,^melodypsg,.:adc).
10.4 Память (Lua)
- Пространство:
dev.spaces[name](напр.cpu.spaces["program"]/"io"/"opcodes"). Чтение::read_u8/16/32/64(addr)(+_iзнаковые), логические:readv_*, прямые:read_direct_*.:read_range(start,end,width[,step])→ бинарная строка (быстрый пакетный дамп — полезно для mem/vram). Запись::write_u8/…(addr,val),:writev_*. Свойства:.name .data_width .address_mask .endianness. - Share/region:
machine.memory.shares[tag]/.regions[tag]/.banks[tag](илиdevice:memshare(tag),device:memregion(tag));:read/write_u8/…,.length .endianness .bitwidth. - ПРИМЕНЕНО В МОСТЕ:
mem/lmem/vram/shareчитают через ОДИНobj:read_range(addr,addr+len-1,8)(вместоlenвызововread_u8с pcall) + быстрый hex по lookup-таблице. Фолбэк на побайтовый цикл, если у объекта нетread_range(например, у share). Проверено: байт-в-байт совпадает с прежним.
10.5 Lifecycle / события (emu) — ВАЖНЫЙ нюанс
- Документированы:
emu.add_machine_{reset,stop,pause,resume,frame,pre_save, post_load}_notifier(cb)→ объект подписки с:unsubscribe()и.is_active; корутинныеemu.wait(sec),emu.wait_next_frame(),emu.wait_next_update(); логиemu.print_{error,warning,info,verbose,debug};emu.subst_env. emu.register_periodic(fn)в доке ОТСУТСТВУЕТ, но реален (luaengine.cpp:933) и КРИТИЧЕН: он качается изemulator_info::periodic_check()в циклеwhile(is_stopped())ДОwait_for_debugger, поэтому работает в hard-stop при любом OSD-дебаггере.add_machine_frame_notifierв стопе МОЛЧИТ. → для pump IPC/опроса в дебаггере используйregister_periodic, а не frame-нотифаер.
10.6 Плагин и манифест
plugin.json:{ "plugin": { "name", "description", "version", "author", "type": "plugin"|"library", "start": "false" } }. Грузится-plugin <name>; путь поиска-pluginspath;start:false→ только по явному запросу.init.lua: таблицаexportsсname/version/description/license/authorи функциейexports.startplugin(); в концеreturn exports. Runtime-объект:manager.plugins[name](.name .description .type .directory .start).- Эталоны:
plugins/gdbstub/и нашplugins/mamebridge/. - Встроенные плагины MAME: autofire, console, data, discord, dummy, gdbstub, hiscore, inputmacro, layout, offscreen reload, timecode, timer.
Язык общения с пользователем: русский.