MAME is the reference for what an arcade machine did. It isn't a library you can link: its drivers are C++ device objects wired to a scheduler you can't bring along. So a port is never "compile the MAME driver". It means using the MAME driver as the specification and rebuilding the game against the SDK.
There are three routes. Choosing the wrong one is the expensive mistake, so start here.
Choose the route
| A: emulate via AAE | B: translate the driver | C: write it fresh | |
|---|---|---|---|
| what runs | the original ROM, on an emulated CPU | your C, reproducing the game's logic | your C, your game |
| fidelity | exact | as good as your reading of the driver | none; it's a new game |
| effort | days, if AAE has the driver | weeks | days |
| needs the romset | yes, on the SD card | usually (art, tables, levels) | no |
| CPU budget | the whole emulated machine, every frame | almost nothing | nothing |
| best for | vector games AAE already covers | games whose logic is simple and whose data is the value | anything |
Route A is the default for a vector arcade game. It's what the Tac/Scan example in the starter kit is, and Porting an AAE game covers it end to end.
Read the MAME driver first
Whatever the route, the driver file (src/mame/drivers/<name>.cpp plus its video/ and audio/ siblings) is the specification. Six things to extract:
| in MAME | tells you | where it lands |
|---|---|---|
ROM_START / ROM_LOAD | which chip image goes to which address | the ROM table |
| the address maps | the memory layout and which ranges are I/O | the handler table |
| the CPU and its clock | which CPU, at what frequency | driver[].cpu_type / cpu_freq |
| the interrupt wiring | how often, and IRQ or NMI | cpu_int_type, cpu_intpass_per_frame |
| the refresh rate or crystal derivation | the game's speed | driver[].fps and uvm2_set_refresh() |
the input ports (PORT_START / PORT_BIT) | bit layout and polarity per port | getport() |
Take the refresh rate from the derivation, not the rounded comment. Tac/Scan is 15,468,480 ÷ 3 ÷ 0x1f788 = 40.00 Hz exactly. On these machines the refresh rate is the game speed: at 42.8 fps it played 7% fast.
Take input polarity from the port definitions, not from what looks natural. Active-low with a non-zero idle value is common and varies per port within one machine. Get it wrong and the controls silently do nothing.
Is it a vector game?
This is the question that decides whether a port is a port or a project.
Vector hardware (Atari's AVG/DVG, Cinematronics' CCPU, Sega's G80, Vectorbeam) emits line segments, the same shape as what the Vectrex draws. MAME marks these as VECTOR screens with a vector_device.
Raster hardware has a framebuffer, tiles and sprites, and the Vectrex has none of those. Converting one is a content problem: something has to turn each frame's tile and sprite state into line art, inside the frame budget. That's a per-game pipeline, and it isn't in the SDK. The SDK draws the lines you give it.
The closest thing on offer is v_directGapped, which draws one ramp with up to 16 holes along it, so a row of pixels costs one ramp instead of one stroke per lit run. Treat it as a tool, not a raster solution.
Route A: through AAE
Covered in Porting an AAE game. The MAME-specific notes:
-
AAE's drivers are MAME ports, usually of an older MAME. Where they disagree, MAME is almost always right. Read both.
-
The ROM table is a transcription of
ROM_START:MAME SDK ROM table ROM_LOAD("f", addr, len, CRC)a row with AAE_ROM_PLAINROM_LOAD16_BYTE(even / odd)AAE_ROM_EVEN/AAE_ROM_ODDROM_CONTINUE(addr, len)a second row with src_offsetROM_RELOAD(addr, len)a row with a NULLfileROM_REGION(len, ...)one GI[]array, sized to what is actually readThe member names are the zip's, not MAME's labels. Check the zip.
-
ROM_REGIONsizes are an upper bound. AAE allocated 64 KB for a Tac/Scan region whose highest read is at 1022. Sizing it to0x400is part of what makes the game fit. Find the real high-water mark with-DAAE_ACCESS_COUNTon the host. -
Only the Z80 core ships with the kit. The 6502, 6809 and 68000 cores are headers only; bring the
.cfiles in from AAE.
Route B: translate the driver
When the game's logic is simple and its data is what matters (level layouts, shapes, tables), emulating a whole CPU to run it is paying a lot for little. Translating means writing the game in C and keeping the original's data. It's more work and it isn't exact. Do it when route A doesn't fit the budget, or when the machine is raster and you'll redraw the art anyway.
What the SDK gives you:
- The draw path, unchanged. Nothing in the SDK knows about AAE. Call
v_directDraw32or libvpy. aae_rom_load_bases(ops, n, bases), the romset loader against your own arrays instead of AAE'sGI[]. The zip still gets its CRC checked.aae_memdispatch.h, per-page memory dispatch with a fast path, if you end up emulating part of a machine.aae_text(s, y, scale), one centred line in the loader's small vector font, for error screens.
The method:
- Run the game in MAME with its debugger and use it as ground truth: traces, memory watchpoints, tile and sprite state.
- Find the data: level tables, object tables, shapes, strings. Extract them from the romset at the addresses MAME shows you.
- Write the logic against the data, one screen at a time, comparing with MAME as you go.
- Verify by comparison, not by eye. Hash what you draw, or dump a frame and diff it. The Tac/Scan host harness (
tools/host_test.c) shows the pattern: a fake SDK, an FNV hash over every coordinate, and a frame count.
The trap this route keeps hitting: translating a switch statement, a jump table or a dispatch chain drops cases silently. One port shipped with a
casemissing itsbreak, which turned an opcode into a no-op and froze the scoreboard. It was only found by diffing a PC trace against the original. If you translate a dispatch, verify it with a trace.
Route C: write the game
Start from examples/hello_uvmc2 plus libvpy. Nothing from MAME is involved, and the budget stops being a question. It's worth naming because "port X" often turns out to mean "I want a game that feels like X on a Vectrex", and that's enormously cheaper.
Whatever the route: the budget
A 50 Hz frame is 30,000 bus cycles, and no amount of CPU changes that. Within it:
- a chained stroke costs 6 commands and 32 to about 150 bus cycles, depending on its length;
- a stroke that needs a blanked jump first costs 2.4–3× that;
- the emulated CPU (route A) and building the list both have to finish within the frame, though dual core overlaps them with the beam.
So the first estimate for any port is: how many strokes does one frame of this game draw? A MAME vector driver's display list tells you. Count its strokes and its separate figures, and put them into the frame budget estimator. Then measure properly with uvm2_list_count (Host tools).
ROMs
The starter kit ships one romset, for its worked example. Any other game's romset is the porter's to obtain and be entitled to. Nothing in the SDK embeds a ROM: as with MAME, the image reads roms/<game>.zip from the SD card at startup, checks its CRC, and says so on screen if it's missing.