joeylib2/scripts/verify-iigs-joy.sh

237 lines
8 KiB
Bash
Executable file

#!/usr/bin/env bash
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
# See LICENSE.md for license information.
# SPDX-License-Identifier: MIT
#
# verify-iigs-joy.sh - Joystick gate for the IIgs: the stick on the game
# port, every direction, both buttons and every release, driven through
# MAME's joystick and read back from what examples/joy draws.
#
# The port (src/iigs/input.c) reads paddles 0 and 1 (the asm in
# joeyDraw.s) and the buttons at $C061/$C062 as JL_JOYSTICK_0 (stick A); until
# a program calls jlJoystickReset a paddle reads as a digital direction.
# The IIgs exposes one stick: B must read unplugged throughout. MAME's
# joystick (-gameio joy) is held from Lua at each step's paddle positions
# (0 and 255 full, 128 at rest) and buttons, and a snapshot taken once JOY
# has drawn it; the snapshots, lines doubled (MAME's IIgs frame has
# double-width pixels), then go through tests/gate/joyScreen.c
# (scripts/lib/joyGate.sh) in order. Escape must then end JOY.
#
# The program boots from a GS/OS floppy whose launcher starts it, with its
# data disk beside it (scripts/make-iigs-boot-disk.sh, make-iigs-disk.sh),
# as joey-run boots it.
#
# Usage: scripts/verify-iigs-joy.sh
# Env: JOY_SHOTS also keep the screenshots in this folder
# Requires: MAME, the IIgs firmware and GS/OS, the IIgs toolchain (the disk
# scripts), ImageMagick; build/iigs/bin/JOY
# (make iigs-disk).
set -uo pipefail
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
source "$repo/scripts/lib/joeyEnv.sh"
source "$repo/scripts/lib/joyGate.sh"
joeyNeed MAME mame
joeyNeed IIGS_ROMPATH iigsFirmware
joeyNeed GSOS_SYSTEM_DISK iigsFirmware
joeyNeed CADIUS cadius
JOY_LABEL=verify-iigs-joy
JOY_REST_B="$JOY_ABSENT"
# The checks read snapshots MAME has already taken: no waiting, and none
# taken again.
JOY_PRESS_SECONDS=0
JOY_SHOT_TRIES=1
program="$repo/build/iigs/bin/JOY"
wall=300 # hard cap on MAME, whatever happens to this script
readySeconds=80 # emulated: GS/OS booting and JOY drawing
settleSeconds=0.5 # emulated: a change until JOY has drawn it
exitSeconds=3 # emulated: Escape until JOY's screen is gone
paddleLow=0
paddleRest=128
paddleHigh=255
[ -f "$program" ] || { echo "$JOY_LABEL: build JOY first (make iigs-disk)" >&2; exit 2; }
JOY_WORK=$(mktemp -d -t joeylib-iigs-joy.XXXXXX)
mame=""
cleanup() {
joeyStop "$mame" 2> /dev/null
rm -rf "$JOY_WORK"
}
trap cleanup EXIT
# The steps checked: stick A's, B left alone.
steps=("${JOY_STEPS_TWO_STICKS[@]:0:9}")
# state CONTROL... -- the Lua table of paddle X, paddle Y, button 0 and
# button 1 with the controls held.
state() {
local x=$paddleRest
local y=$paddleRest
local b0=0
local b1=0
local control
for control in "$@"; do
case "$control" in
A-left) x=$paddleLow ;;
A-right) x=$paddleHigh ;;
A-up) y=$paddleLow ;;
A-down) y=$paddleHigh ;;
A-button0) b0=1 ;;
A-button1) b1=1 ;;
*) joyFail "no control $control on the IIgs" ;;
esac
done
printf '{%s, %s, %s, %s}' "$x" "$y" "$b0" "$b1"
}
# The snapshots, in the order the checks take them: rest, then for each
# step held and released.
shotIndex=0
joyPress() {
:
}
joyRelease() {
:
}
joyShot() {
local shot
shot=$(ls "$JOY_WORK"/snap/*.png 2> /dev/null | sort | sed -n "$((shotIndex + 1))p")
shotIndex=$((shotIndex + 1))
[ -n "$shot" ] || return 1
convert "$shot" -sample 100%x200% "$1"
}
states="$(state)"
for step in "${steps[@]}"; do
IFS='|' read -r _ held _ _ <<< "$step"
read -r -a controls <<< "$held"
states="$states, $(state "${controls[@]}"), $(state)"
done
mkdir -p "$JOY_WORK/cfg" "$JOY_WORK/nvram" "$JOY_WORK/snap"
BINDIR="$(dirname "$program")" JOEY_DISK_EXAMPLES="$(basename "$program")" \
"$repo/scripts/make-iigs-disk.sh" "$JOY_WORK/data.2mg" > "$JOY_WORK/disk.log" 2>&1 ||
{ echo "$JOY_LABEL: FAIL - could not build the data disk" >&2; tail -5 "$JOY_WORK/disk.log" >&2; exit 1; }
"$repo/scripts/make-iigs-boot-disk.sh" "$JOY_WORK/boot.po" "$(basename "$program")" > "$JOY_WORK/boot.log" 2>&1 ||
{ echo "$JOY_LABEL: FAIL - could not build the boot disk" >&2; tail -5 "$JOY_WORK/boot.log" >&2; exit 1; }
cat > "$JOY_WORK/joy.lua" << LUA
local states = { $states }
local readyAt = $readySeconds
local settle = $settleSeconds
local exitAfter = $exitSeconds
-- A field of an ioport whose name contains pattern, or nil.
local function field(tag, pattern)
local p = manager.machine.ioport.ports[tag]
if not p then return nil end
for name, f in pairs(p.fields) do
if name:find(pattern, 1, true) then return f end
end
return nil
end
-- The field of an ioport that drives mask, or nil.
local function maskField(tag, mask)
local p = manager.machine.ioport.ports[tag]
if not p then return nil end
for _, f in pairs(p.fields) do
if f.mask == mask then return f end
end
return nil
end
-- A field named exactly name on any port whose tag starts with prefix.
local function key(prefix, name)
for tag, p in pairs(manager.machine.ioport.ports) do
if tag:sub(1, #prefix) == prefix and p.fields[name] then return p.fields[name] end
end
return nil
end
local fx = field(":gameio:joy:joystick_1_x", "")
local fy = field(":gameio:joy:joystick_1_y", "")
-- The game port's switches SW0 and SW1: bits 4 and 5 of MAME's button
-- port (its player numbers do not follow the switches).
local fb0 = maskField(":gameio:joy:joystick_buttons", 0x10)
local fb1 = maskField(":gameio:joy:joystick_buttons", 0x20)
local esc = key(":macadb:", "Esc")
if not (fx and fy and fb0 and fb1 and esc) then
for tag, p in pairs(manager.machine.ioport.ports) do
for name, f in pairs(p.fields) do
if tag:find(":gameio:", 1, true) or name:lower():find("esc", 1, true) then
print(string.format("JOYGATE field %s %s mask %x", tag, name, f.mask))
end
end
end
print("JOYGATE: MAME's joystick or Escape key not found")
manager.machine:exit()
return
end
local i = 0
local nextAt = readyAt
local phase = "apply"
local escAt = nil
emu.register_frame_done(function()
local t = manager.machine.time:as_double()
if t < nextAt then return end
if phase == "apply" then
i = i + 1
if i > #states then
esc:set_value(1)
escAt = t
phase = "escape"
nextAt = t + 0.5
return
end
local s = states[i]
fx:set_value(s[1]); fy:set_value(s[2]); fb0:set_value(s[3]); fb1:set_value(s[4])
phase = "shoot"
nextAt = t + settle
elseif phase == "shoot" then
manager.machine.video:snapshot()
phase = "apply"
nextAt = t
elseif phase == "escape" then
esc:set_value(0)
phase = "after"
nextAt = escAt + exitAfter
elseif phase == "after" then
manager.machine.video:snapshot()
manager.machine:exit()
end
end)
LUA
(cd "$JOY_WORK" && exec env SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy timeout -s KILL "$wall" "$MAME" apple2gs \
-rompath "$IIGS_ROMPATH" -flop3 "$JOY_WORK/boot.po" -flop4 "$JOY_WORK/data.2mg" -gameio joy \
-skip_gameinfo -cfg_directory "$JOY_WORK/cfg" -nvram_directory "$JOY_WORK/nvram" \
-snapshot_directory "$JOY_WORK/snap" -snapname "shot%i" \
-video none -sound none -nothrottle -autoboot_script "$JOY_WORK/joy.lua") > "$JOY_WORK/mame.log" 2>&1 &
mame=$!
wait "$mame"
mame=""
if grep -q '^JOYGATE' "$JOY_WORK/mame.log"; then
echo "$JOY_LABEL: FAIL - $(grep '^JOYGATE:' "$JOY_WORK/mame.log")" >&2
grep '^JOYGATE field' "$JOY_WORK/mame.log" >&2
exit 1
fi
joyCheck "rest" "A=$JOY_REST B=$JOY_REST_B"
joyRunSteps "${steps[@]}"
# The last snapshot, after Escape: JOY gone.
if joyShot "$JOY_WORK/after.png" && "$(joeyTool joyScreen)" "$JOY_WORK/after.png" > /dev/null 2>&1; then
joyFail "Escape did not end JOY"
fi
joyResult "$JOY_LABEL" "the game port's stick, every direction, both buttons and every release, B unplugged; Escape ends JOY"