Inspecting MAME Vector Displays with Lua Device Notifiers
Use MAME's vector-device callbacks to measure beam segments, blanked moves, and intensity without confusing renderer effects with emulated hardware.
Vector arcade games do not produce a conventional raster image. Their emulated display device describes movement of a beam between points, including visible segments and blanked repositioning. MAME exposes that vector-device activity to Lua, which makes it possible to inspect the geometry a driver is handing to the renderer without editing the C++ driver.
This is useful when a game draws an unexpected line, a point is missing, a display appears too dim, or a visual effect changes between renderers. It also imposes an important boundary: Lua observes the vector device’s callbacks, not every analog characteristic of an original monitor. Beam persistence, spot size, phosphor color, deflection timing, and real CRT aging are not directly measured by a line callback.
Find the emulated vector device
MAME’s Lua device collection exposes vector devices through manager.machine.vector_devices[tag]. The tag depends on the emulated machine configuration; do not assume every vector driver uses the same name. Inspect the target machine’s device tree or use the debugger and device-inspection facilities described in MAME’s Lua reference before choosing the tag.
The callback model distinguishes visible beam segments from zero-intensity movement. A line notification provides the previous and current beam positions, color, 8-bit intensity, and virtual display dimensions. A move notification reports a reposition with the beam off. Frame-begin and frame-end notifications let a script reset and summarize counters.
Count visible lines and blanked moves
This short MAME Lua example assumes the vector-device tag has been verified and replaced below. It retains the returned notifier subscriptions, counts callback events, and prints one summary every 60 emulated vector frames so diagnostic output does not flood the console.
local vectorTag = ":vector" -- Replace with the tag used by this machine.
local vector = manager.machine.vector_devices[vectorTag]
if not vector then
print("No vector device at tag " .. vectorTag)
return
end
local visibleLines = 0
local blankedMoves = 0
local intensityTotal = 0
local frameNumber = 0
local subscriptions = {}
subscriptions[#subscriptions + 1] =
vector:add_frame_begin_notifier(function()
visibleLines = 0
blankedMoves = 0
intensityTotal = 0
end)
subscriptions[#subscriptions + 1] =
vector:add_line_notifier(function(lastx, lasty, x, y, color,
intensity, width, height)
visibleLines = visibleLines + 1
intensityTotal = intensityTotal + intensity
end)
subscriptions[#subscriptions + 1] =
vector:add_move_notifier(function(x, y, color, width, height)
blankedMoves = blankedMoves + 1
end)
subscriptions[#subscriptions + 1] =
vector:add_frame_end_notifier(function()
frameNumber = frameNumber + 1
if frameNumber % 60 == 0 then
local mean = 0
if visibleLines > 0 then
mean = intensityTotal / visibleLines
end
print(string.format(
"vector frames=%d visible=%d blanked=%d mean-intensity=%.1f",
frameNumber, visibleLines, blankedMoves, mean))
end
end)
The mean intensity here is a simple unweighted mean of callback intensity values; it is not an energy measurement. Long and short line segments contribute equally, color channels are not analyzed, and the callbacks do not provide the hardware’s beam dwell time. Treat the output as a diagnostic counter, not a photometric model.
Separate emulation from presentation
First test with MAME’s normal vector rendering path and a stable game state. Record whether the driver emits the expected visible and blanked moves, their coordinates, and the color and intensity values. Then enable optional rendering effects one at a time. The BGFX vector CRT renderer adds persistent phosphor accumulation, beam shaping, bloom, and tone mapping; those are presentation stages applied after the emulated vector primitives are available.
If a line is present in the callback data but absent on screen, investigate renderer configuration, display scaling, clipping, and color/intensity handling. If the callback itself is missing or has unexpected coordinates, inspect the driver and emulated vector device before changing a CRT effect. A filtered or tone-mapped output can conceal evidence about the source primitives.
Use controlled checkpoints: an attract-mode frame, a static menu, a known bright segment, and a state reload. Compare outputs at the same emulated frame and renderer settings. Save the game name, MAME version, Lua script revision, device tag, vector counters, and screenshots together; otherwise two runs may differ because they sampled different frames rather than because a code change fixed the problem.
Limits and performance
Notifiers run during vector processing, so callbacks should do bounded work. Avoid writing one log line per segment during long sessions. Aggregate counters, sample selected coordinates, and reduce output frequency. Retain the subscription objects while the diagnostics are active and stop or remove the script when the experiment ends.
The vector device is an emulated interface to a display subsystem, not an oscilloscope. A high-level line list cannot reveal every blanked slew limit, analog overshoot, color decay curve, or original monitor fault. MAME’s vector rendering documentation describes the approximations used by its BGFX pipeline; use that implementation documentation when interpreting renderer behavior.
This separation makes diagnosis much clearer: Lua can establish what vector commands the emulated device produced, while renderer testing establishes how MAME turned those commands into pixels.
Read the callback stream as geometry, not a screenshot
A line notifier describes a beam move from a previous point to a new point while intensity is non-zero. A move notifier describes a reposition with zero intensity. Treat each event as a primitive in the emulated display’s coordinate system. The width and height arguments describe the configured virtual vector display dimensions; they are useful for normalizing coordinates, but they do not promise a particular physical monitor resolution or aspect ratio. The callback’s 8-bit intensity is an input to rendering, not a calibrated luminance value.
When comparing two captures, normalize coordinates by the reported dimensions and keep the raw coordinates too. A uniform translation can point to a viewport or origin issue; an axis-specific scale difference can indicate aspect or display configuration; a missing callback can indicate the driver or vector generator never emitted that primitive. Do not infer that every visual pixel corresponds one-to-one with a line event. Rasterization, clipping, beam width, blending, phosphor persistence, and post-processing all occur in the presentation path.
The line count is not a measure of frame quality or game correctness by itself. A vector game may intentionally draw a different number of segments in a new scene, and the same path can be subdivided differently by a driver implementation while looking equivalent. Pair counts with selected coordinate samples, state checkpoints, screenshots, and source inspection. If comparing versions, collect the same deterministic game state and compare segment endpoints and intensities within an explicit tolerance rather than asserting that every count must be identical.
Turn a probe into a useful regression test
Start with one driver whose vector-device tag and expected screen orientation are known. Verify that the script discovers exactly one intended device, then record callback totals and a bounded sample of the first few line and move events. The sample should include frame number, endpoints, color, intensity, and virtual dimensions. Do not make the sample unlimited: a single busy frame can emit many lines, and diagnostic output is not a substitute for a full capture format.
Use a controlled scene, such as a stable attract-mode frame or a repeatable test state, and run the probe several times. If it reports the same event stream but screenshots differ, investigate renderer options and host display state. If the stream changes, inspect the emulated program state and driver before attributing it to BGFX. A regression should identify which layer owns the observed change and carry a minimal script plus reproducibility notes.
Treat notifier lifetime deliberately. Keep returned subscriptions referenced for as long as callbacks should remain installed, and stop the diagnostic session when the measurement is complete. If you add expensive geometry processing, do it after collecting a bounded trace rather than inside the callback. A small amount of disciplined instrumentation provides stronger evidence than a high-overhead overlay that changes the conditions being measured.
Related:
- MAME Lua Scripting: Inspecting Emulated Hardware Without Editing the Driver
- CRT Shaders and Integer Scaling: Making Old Pixels Look Right on New Screens
Sources: