MAME I/O Ports: Mapping Host Controls to Emulated Hardware Inputs
Trace MAME input from host devices through configurable sequences into emulated I/O port fields, including active-low and analog behavior.
MAME separates the physical or host-side control from the signal seen by an emulated machine. A keyboard key, gamepad button, axis, or lightgun item is read by an input provider; MAME maps that item through a configurable input sequence; an I/O port field then turns the result into the bit or analog value expected by the emulated hardware.
This layered model explains why “the button is pressed” is not enough to diagnose an input bug. A host device may be detected while a configured sequence is wrong, the emulated field may be active-low, a DIP switch may be a configuration value rather than a live control, or a driver-defined tag may differ from the one a script expects.
Port, field, and host control are different objects
An I/O port groups bits as the emulated device exposes them. A field describes a portion of the port and its meaning: a digital switch, an analog axis, a DIP switch, an adjuster, or another special value. The driver’s input-port definitions choose masks, defaults, polarity, and player assignment. Those definitions are the authoritative map for a specific game or system.
The host input manager tracks devices and configured sequences. MAME’s Lua interface exposes the emulated ports through manager.machine.ioport.ports, indexed by absolute tag. The tag is driver-defined and can vary by machine. A read-only inspection script should first enumerate available tags instead of assuming a universal name:
local ioport = manager.machine.ioport
for tag, port in pairs(ioport.ports) do
print(string.format("port=%s value=0x%08x active=0x%08x",
tag, port:read(), port.active))
end
Use this as a diagnostic, not as a game-independent mapping file. port:read() returns the current 32-bit emulated port value; port.active identifies bits belonging to active fields. A zero value can still be the correct state when the port is active-low or its default value encodes an inactive switch.
Digital, analog, and configuration fields
Digital fields behave like switches, but the electrical polarity is represented through the field’s default and active state. Do not infer “pressed equals one” from a raw port dump. Compare the field mask and default in the driver’s definitions, and observe the actual emulated behavior while changing one assigned host input at a time.
Analog fields have range, neutral, sensitivity, and sometimes wrapping semantics. Absolute axes report position; relative axes report movement since the last update. MAME can combine axis input with increment/decrement buttons, and sensitivity changes the effect of those controls. A mouse or wheel that appears to jump or drift may therefore be a mapping, centering, or sensitivity issue rather than a broken emulated port.
DIP switches and configuration fields are usually set through MAME’s UI and should not be treated as ordinary host buttons. Lua field:set_value() is a programmatic override for debugging or scripting; field:clear_value() restores normal behavior. Such overrides can invalidate a reproducibility test if they are left in a script or save state.
A reproducible input investigation
Start from a known configuration and record the MAME version, system short name, input provider, controller profile, and relevant cfg files. Confirm the host device appears, inspect the driver’s I/O port tags and fields, then capture the raw port before and after pressing a single control. Check whether the field is active-low, whether another control shares a sequence, and whether the game’s player position matches the configured controller.
Use MAME’s input configuration UI for normal remapping. Keep Lua overrides confined to a temporary debug script, clearly log when an override is installed, and call clear_value() during cleanup. Re-test after removing the script and restarting the machine. A control that works only with a debug override is evidence about the mapping layer, not proof that the default configuration is correct.
Separate low-level gameplay inputs from text entry and high-level UI actions; MAME handles natural keyboard and UI input through related but distinct interfaces. This matters for systems where entering a name, using a menu hotkey, and pressing an emulated keyboard key look similar to the user but follow different code paths.
Interpret the port value with the driver’s mask
An input port is a packed value. Each field owns a mask, and the field’s default value encodes its inactive or neutral state. On common active-low arcade wiring, an unpressed button bit is high and a pressed button pulls the bit low. A raw value of 0xFE on a port whose low bit is an active-low button can therefore mean that button is pressed, not released. In another driver, the same bit position can have different meaning or polarity. Always read the actual port declaration before interpreting a hexadecimal trace.
The declaration’s mask also tells you whether a bit is assigned at all. A port value includes defaults for fields that are inactive; port.active is a mask of bits corresponding to active fields rather than a Boolean saying that a player is pressing something. This is why reading a whole port and comparing it to zero is usually a poor test. Compare (value & field.mask) against the field’s documented default and active value, or query the field through MAME’s input UI and Lua reference.
Drivers express these details with input-port macros. A schematic active-low button declaration looks like this:
PORT_START("P1")
PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_BUTTON1) PORT_PLAYER(1)
This declaration says that the P1 port has a button field at bit zero, active when the electrical bit is low, assigned to player one. It does not dictate which host key or controller button should drive it; that is provided through MAME’s default input assignments and user configuration. Do not copy this example into a real driver without checking its port width, existing masks, and wiring documentation.
Understand sequence composition and timing
An assigned input is a sequence of host input codes, not necessarily one physical button. MAME can combine controls using logical AND/OR and negation. For switch sequences, a sequence is evaluated as sum-of-products logic; for axis sequences, switch conditions can gate axis values, and absolute-axis values take precedence over relative-axis values within a group when non-zero. A mapping that appears to “stick” may contain a modifier or a second binding rather than a faulty controller.
MAME’s input system updates I/O port fields once per video frame produced by the first screen in the emulated system. That matters for fast pulse behavior, impulse inputs, light guns, and relative devices. The host provider can sample at one rate while the emulated port changes at another. A trace captured at arbitrary host intervals may miss a short impulse or misrepresent a relative delta that is reset at the frame boundary. Instrument the emulated frame and use the same test sequence on each run.
Analog configuration requires a separate mental model from digital switches. Absolute axes represent position within a range, relative axes represent movement since the prior update, and increment/decrement buttons can adjust an analog field as a fallback. Sensitivity, key delta, centering, reversal, wrapping, and player assignment all affect the value delivered. For a trackball, first establish whether the field wraps and resets every frame; for a pedal, determine whether greater host travel should increase or decrease the emulated number. Changing a global controller sensitivity before identifying the field can make unrelated games worse.
Use Lua as an observation layer, not a new default
MAME’s Lua API can enumerate manager.machine.ioport.ports, read each port as a 32-bit value, inspect port.active, list fields, and examine metadata such as field.mask, field.defvalue, field.player, field.sensitivity, and field.analog_reverse. Use those capabilities to generate a machine-specific inventory, then compare it with the input-port definitions in that driver’s source. A script should report the machine short name and actual tags so its output cannot be mistaken for a universal mapping.
field:set_value() is useful for controlled experiments, but it deliberately overrides ordinary input behavior. It can prove that a downstream game action responds when a field is forced active; it does not prove that the host device, sequence, player mapping, or default field wiring is correct. Pair every override with a cleanup path using field:clear_value(), and rerun the same test after removing the script.
Configuration is layered. MAME has general input defaults and machine-specific assignments, and users can change them through the in-emulator input configuration UI. Compare the visible binding with the effective sequence, not just one file on disk. A controller provider may assign a different device number after hardware is reconnected, and a name shown in a UI is not guaranteed to be a globally unique hardware identity. Rebind through the UI when possible, then keep a copy of the relevant configuration and note the exact MAME build.
A repeatable acceptance test
For each failing control, define one expected transition, for example “pressing the first button changes mask 0x01 from its inactive default to its active value, and releasing restores it.” Start MAME with the normal configuration, capture the unpressed port, press only that control, hold it long enough to span an emulated frame, and capture again. Repeat with a second host control to detect accidental duplicate bindings. For analog controls, log the raw host value and emulated field range at several known physical positions instead of recording only the endpoints.
Then test the in-game result, because a valid field transition may still be ignored by game logic when a menu, cabinet mode, cocktail setting, or player turn changes. Reboot and repeat without Lua overrides. A passing test should preserve the MAME version, machine name, input provider, relevant config, field tag/mask, before/after values, and observed in-game action. This evidence localizes whether the defect is in host detection, assignment, polarity, timing, driver declaration, or game-state logic.
Related:
- MAME Input Recordings: Deterministic Playback, Desyncs, and Reproducible Evidence
- MAME Lua Scripting: Inspecting Emulated Hardware Without Editing the Driver
Sources: