joeylib2/scripts/verify-amiga-palette.sh
2026-10-13 14:01:46 -05:00

123 lines
4.4 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-amiga-palette.sh - the Amiga's palette and SCB changes through the
# copper: fast, and right on the screen.
#
# The Amiga shows each SCB band's palette with a user copper list, built and
# merged by graphics.library (MrgCop, LoadView, then a WaitTOF) -- about three
# frames on an A500. A change that leaves every band where it was (a palette
# changing, or a band given another palette) patches the colour MOVEs of the
# installed list in place instead (src/amiga/hal.c, patchBandColors). The
# stage hashes cannot see any of this: they hash the surface, not the copper.
#
# PALPROBE (examples/palprobe; make -f make/amiga.mk build/amiga/test/Palprobe)
# runs under FS-UAE on an A500. It logs how many palette changes, and SCB
# changes, it presented a second; the palette rate must be at least
# MIN_PALETTE_RATE (one rebuild a change gave 16). Then it holds a screen of
# four bands after one palette change and one same-layout SCB change, and
# the screenshot's bands must be, top to bottom, red, green, yellow, red.
#
# Usage: scripts/verify-amiga-palette.sh
# Env: TIMEOUT seconds to wait for the probe (default 120)
# Exit: 0 PASS, 1 FAIL, 2 SKIP (FS-UAE or the Kickstart not installed)
set -uo pipefail
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
source "$repo/scripts/lib/joeyEnv.sh"
joeyNeed FSUAE fsuae
source "$repo/scripts/lib/amigaGate.sh"
amigaMachine
timeout_s=${TIMEOUT:-120}
probe=$repo/build/amiga/test/Palprobe
label=verify-amiga-palette
# Palette changes presented a second: 16 when every change rebuilt the
# copper list, ~990 patching it in place (A500, 2026-10-06).
MIN_PALETTE_RATE=500
make -s -f "$repo/make/amiga.mk" "$probe" || {
echo "$label: FAIL - building Palprobe failed" >&2
exit 1
}
work=$(mktemp -d -t joeylib-amiga-palette.XXXXXX)
mkdir -p "$work/s"
cp "$probe" "$work/"
printf 'stack 32768\n:Palprobe\n' > "$work/s/startup-sequence"
display=:$((RANDOM % 400 + 1300))
Xvfb "$display" -screen 0 800x600x24 -nolisten tcp > /dev/null 2>&1 &
xvfb_pid=$!
fs_pid=
cleanup() {
[[ -n $fs_pid ]] && joeyStop "$fs_pid"
kill "$xvfb_pid" 2>/dev/null || true
wait 2>/dev/null || true
rm -rf "$work"
}
trap cleanup EXIT
sleep 0.5
DISPLAY=$display "$FSUAE" "${AMIGA_MACHINE_ARGS[@]}" --fast_memory=2048 \
--hard_drive_0="$work" --hard_drive_0_label=JOEYLIB \
--automatic_input_grab=0 \
--fullscreen=0 --fade_out_duration=0 > "$work/fsuae.log" 2>&1 &
fs_pid=$!
log=$work/joeylog.txt
for ((waited = 0; waited < timeout_s; waited++)); do
if grep -q "PALPROBE: ready" "$log" 2>/dev/null; then
break
fi
sleep 1
done
if ! grep -q "PALPROBE: ready" "$log" 2>/dev/null; then
echo "$label: FAIL - the probe never reached its held screen within ${timeout_s}s" >&2
exit 1
fi
# Two seconds into the hold: the copper has run the patched list.
sleep 2
DISPLAY=$display import -window root "$work/screen.png"
grep "PALPROBE:" "$log"
python3 - "$work/screen.png" "$log" "$MIN_PALETTE_RATE" "$label" <<'PY'
import re
import sys
from PIL import Image
png, log, minRate, label = sys.argv[1], sys.argv[2], int(sys.argv[3]), sys.argv[4]
rate = None
for line in open(log, encoding="ascii", errors="replace"):
m = re.search(r"PALPROBE: palette\+present: \d+ in \d+ ms = (\d+)/s", line)
if m:
rate = int(m.group(1))
if rate is None:
print(f"{label}: FAIL - no palette rate in the log")
sys.exit(1)
# The bands down the middle column, top to bottom, black (the border)
# dropped: each run of one colour is a band.
img = Image.open(png).convert("RGB")
w, h = img.size
named = {(255, 0, 0): "red", (0, 255, 0): "green", (255, 255, 0): "yellow"}
bands = []
for y in range(h):
rgb = img.getpixel((w // 2, y))
if rgb == (0, 0, 0):
continue
name = named.get(rgb, "other " + str(rgb))
if not bands or bands[-1] != name:
bands.append(name)
want = ["red", "green", "yellow", "red"]
fails = []
if rate < minRate:
fails.append(f"palette changes presented {rate}/s, want at least {minRate}")
if bands != want:
fails.append(f"bands {bands}, want {want}")
if fails:
print(f"{label}: FAIL - " + "; ".join(fails))
sys.exit(1)
print(f"{label}: PASS (palette changes {rate}/s; bands {', '.join(want)} after a palette change and a same-layout SCB change)")
PY