All SDK docs

Porting from MAME

Using a MAME driver as the specification for a Vectrex port, and choosing between emulation, translation, or writing the game fresh.

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 AAEB: translate the driverC: write it fresh
what runsthe original ROM, on an emulated CPUyour C, reproducing the game's logicyour C, your game
fidelityexactas good as your reading of the drivernone; it's a new game
effortdays, if AAE has the driverweeksdays
needs the romsetyes, on the SD cardusually (art, tables, levels)no
CPU budgetthe whole emulated machine, every framealmost nothingnothing
best forvector games AAE already coversgames whose logic is simple and whose data is the valueanything

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 MAMEtells youwhere it lands
ROM_START / ROM_LOADwhich chip image goes to which addressthe ROM table
the address mapsthe memory layout and which ranges are I/Othe handler table
the CPU and its clockwhich CPU, at what frequencydriver[].cpu_type / cpu_freq
the interrupt wiringhow often, and IRQ or NMIcpu_int_type, cpu_intpass_per_frame
the refresh rate or crystal derivationthe game's speeddriver[].fps and uvm2_set_refresh()
the input ports (PORT_START / PORT_BIT)bit layout and polarity per portgetport()

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:

    MAMESDK ROM table
    ROM_LOAD("f", addr, len, CRC)a row with AAE_ROM_PLAIN
    ROM_LOAD16_BYTE (even / odd)AAE_ROM_EVEN / AAE_ROM_ODD
    ROM_CONTINUE(addr, len)a second row with src_off set
    ROM_RELOAD(addr, len)a row with a NULL file
    ROM_REGION(len, ...)one GI[] array, sized to what is actually read

    The member names are the zip's, not MAME's labels. Check the zip.

  • ROM_REGION sizes are an upper bound. AAE allocated 64 KB for a Tac/Scan region whose highest read is at 1022. Sizing it to 0x400 is part of what makes the game fit. Find the real high-water mark with -DAAE_ACCESS_COUNT on the host.

  • Only the Z80 core ships with the kit. The 6502, 6809 and 68000 cores are headers only; bring the .c files 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_directDraw32 or libvpy.
  • aae_rom_load_bases(ops, n, bases), the romset loader against your own arrays instead of AAE's GI[]. 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:

  1. Run the game in MAME with its debugger and use it as ground truth: traces, memory watchpoints, tile and sprite state.
  2. Find the data: level tables, object tables, shapes, strings. Extract them from the romset at the addresses MAME shows you.
  3. Write the logic against the data, one screen at a time, comparing with MAME as you go.
  4. 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 case missing its break, 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.