I used AI to write a little lua script to fix this, it works like a charm. I also made a plugin that can live in the plugins folder and can be enabled perpetually without autolaunching the lua script. I never could have written this on my own. I looked through the code and it doesn't have anything else going on, but use only if you want to. launch with mame -autoboot_script /full/path/to/boulderdash_joystick.lua
which would contain
-- license:BSD-3-Clause
--
-- Boulder Dash joystick timing compatibility fix for MAME Apple II emulation.
--
-- Standalone autoboot-script version.
--
-- This reproduces the KEGS/AppleWin maximum-paddle workaround used by
-- Boulder Dash: when an Apple II joystick axis reaches 255, extend the
-- emulated paddle timer to the equivalent of 287.
--
-- Run with:
-- mame apple2e ... -autoboot_script boulderdash_joystick_fix.lua
local machine = manager.machine
local PDL_UNIT_SEC = 10.8e-6
local NATIVE_MAX = 255
local EXTENDED_MAX = 287
local C064 = 0xc064 -- paddle 0 / joystick X
local C065 = 0xc065 -- paddle 1 / joystick Y
local C070 = 0xc070 -- paddle trigger
local C07F = 0xc07f -- trigger mirrors
local function now()
return machine.time:as_double()
end
local function find_port_by_suffix(suffix)
for tag, port in pairs(machine.ioport.ports) do
if tag == suffix or tag:sub(-#suffix) == suffix then
return port
end
end
end
local joy_x = find_port_by_suffix("joystick_1_x")
local joy_y = find_port_by_suffix("joystick_1_y")
if not joy_x or not joy_y then
return
end
local maincpu = machine.devices[":maincpu"]
if not maincpu or not maincpu.spaces or not maincpu.spaces["program"] then
return
end
local space = maincpu.spaces["program"]
local x_native_expiry = 0.0
local y_native_expiry = 0.0
local x_extended_expiry = 0.0
local y_extended_expiry = 0.0
local function trigger_paddles()
if machine:side_effects_disabled() then
return
end
local t = now()
local x = joy_x:read() & 0xff
local y = joy_y:read() & 0xff
-- Match the 558 one-shot behavior: do not restart an active timer.
if t >= x_native_expiry then
x_native_expiry = t + (x * PDL_UNIT_SEC)
x_extended_expiry =
t + (((x >= NATIVE_MAX) and EXTENDED_MAX or x) * PDL_UNIT_SEC)
end
if t >= y_native_expiry then
y_native_expiry = t + (y * PDL_UNIT_SEC)
y_extended_expiry =
t + (((y >= NATIVE_MAX) and EXTENDED_MAX or y) * PDL_UNIT_SEC)
end
end
local trigger_read_tap
local trigger_write_tap
local paddle_read_tap
trigger_read_tap = space:install_read_tap(
C070, C07F, "Boulder Dash paddle trigger read",
function(offset, data, mask)
trigger_paddles()
return data
end
)
trigger_write_tap = space:install_write_tap(
C070, C07F, "Boulder Dash paddle trigger write",
function(offset, data, mask)
trigger_paddles()
return data
end
)
paddle_read_tap = space:install_read_tap(
C064, C065, "Boulder Dash paddle timing extension",
function(offset, data, mask)
if machine:side_effects_disabled() then
return data
end
local t = now()
if offset == C064
and t >= x_native_expiry
and t < x_extended_expiry then
return data | 0x80
end
if offset == C065
and t >= y_native_expiry
and t < y_extended_expiry then
return data | 0x80
end
return data
end
)
-- Reinstall taps if MAME replaces address-space handlers.
local reinstalling = false
local change_notifier
change_notifier = space:add_change_notifier(function(kind)
if reinstalling then
return
end
reinstalling = true
if trigger_read_tap then
trigger_read_tap:reinstall()
end
if trigger_write_tap then
trigger_write_tap:reinstall()
end
if paddle_read_tap then
paddle_read_tap:reinstall()
end
reinstalling = false
end)