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

132 lines
5.3 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-atarist-palette.sh - The ST family's palette, read off the shifter.
#
# JoeyLib colours are 12-bit $0RGB. A plain ST's shifter has 3 bits per
# channel, so it keeps each channel's top three bits; an STe's has 4, with
# the channel's LOW bit in bit 3 of the register nibble. The ST build (make
# atarist) must write the 3-bit truncation -- on every machine, an STe
# included, since it never uses STe hardware -- and the STe build (make
# ste) the exact 4-bit colour.
#
# Subject: the PATTERN example -- eight bands, each its own palette, each
# palette a gradient whose entry i has i in some of its channels. So every
# register sees every channel value 0..15 across the bands, odd ones
# included, and the Timer B raster ISR reloads all sixteen registers at
# every band. Hatari's video_color trace logs each palette register write
# with its pc; the gate collects the application's (TOS lives at $Exxxxx),
# and for each register the set of values written must be EXACTLY the set
# the machine's encoding of the eight gradients gives. A shifter fed the
# wrong encoding fails on the odd values; a missing band or a wrong
# palette fails on the set.
#
# Usage: scripts/verify-atarist-palette.sh [st|megast|ste|megaste ...]
# machines to run, default "st ste megaste": a plain ST and a
# Mega ST run the ST build, an STe and a Mega STe the STe build
# Requires: ./install.sh install emulator:atarist (Hatari, EmuTOS),
# PATTERN.PRG in build/atarist/bin and build/ste/bin (make
# atarist, make ste)
set -uo pipefail
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
source "$repo/scripts/lib/joeyEnv.sh"
joeyNeed HATARI hatari
joeyNeed EMUTOS_IMG emutos
machines=${*:-st ste megaste}
wall=120 # hard cap on Hatari, whatever happens to this script
runVbls=1200 # EmuTOS boot + PATTERN up and banding: ~600 frames
work=$(mktemp -d -t joeylib-st-palette.XXXXXX)
trap 'rm -rf "$work"' EXIT
fails=0
for machine in $machines; do
case $machine in
st|megast) build=atarist ;;
ste|megaste) build=ste ;;
*) echo "verify-atarist-palette: unknown machine '$machine' (st, megast, ste or megaste)" >&2; exit 2 ;;
esac
prog=$repo/build/$build/bin/PATTERN.PRG
[ -f "$prog" ] || { echo "verify-atarist-palette: missing $prog (run 'make $build')" >&2; exit 2; }
rm -rf "$work/c"
mkdir -p "$work/c"
cp "$prog" "$work/c/"
# The trace is filtered as it is produced: only palette register writes
# from the application, not TOS's ($Exxxxx) or Hatari's other colour
# bookkeeping. --run-vbls ends the run; timeout is the backstop.
SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy timeout -s KILL "$wall" "$HATARI" \
--machine "$machine" \
--tos "$EMUTOS_IMG" \
--harddrive "$work/c" \
--gemdos-drive C \
--drive-a off --drive-b off \
--sound off \
--fast-forward on \
--run-vbls "$runVbls" \
--trace video_color \
--auto 'C:\PATTERN.PRG' 2>&1 \
| grep -E '^write col addr=ff82[45]' | grep -v ' pc=e' > "$work/$machine.log"
python3 - "$work/$machine.log" "$machine" "$build" <<'PY' || fails=$((fails + 1))
import re
import sys
# write col addr=ff825e col=70 video_cyc_w=55892 ... pc=1f00e instr_cyc=12
LINE = re.compile(r'^write col addr=ff82([45][0-9a-f]) col=([0-9a-f]+) ')
PALETTE_BASE = 0x40
COLORS = 16
# PATTERN's eight palettes: which of (red, green, blue) carry the gradient.
GRADIENTS = [(1, 1, 1), (1, 0, 0), (1, 1, 0), (0, 1, 0),
(0, 1, 1), (0, 0, 1), (1, 0, 1), (1, 1, 1)]
def channel(build, v):
"""One 4-bit channel as that build writes the shifter nibble."""
if build == 'atarist':
return v >> 1
return (v >> 1) | ((v & 1) << 3)
path, machine, build = sys.argv[1], sys.argv[2], sys.argv[3]
seen = [set() for _ in range(COLORS)]
writes = 0
for line in open(path, errors='ignore'):
m = LINE.match(line)
if m:
seen[(int(m.group(1), 16) - PALETTE_BASE) // 2].add(int(m.group(2), 16))
writes += 1
bad = []
for i in range(COLORS):
want = set()
for r, g, b in GRADIENTS:
v = channel(build, i) if i else 0
want.add(((v if r else 0) << 8) | ((v if g else 0) << 4) | (v if b else 0))
if seen[i] != want:
bad.append(f"register {i}: wrote {sorted(hex(v) for v in seen[i])}, "
f"want {sorted(hex(v) for v in want)}")
bits = '3-bit' if build == 'atarist' else '4-bit'
if writes == 0:
print(f"verify-atarist-palette: {build} build on {machine} FAIL - PATTERN never wrote the palette", file=sys.stderr)
sys.exit(1)
if bad:
print(f"verify-atarist-palette: {build} build on {machine} FAIL - not the {bits} encoding of PATTERN's palettes:",
file=sys.stderr)
for b in bad[:4]:
print(f"verify-atarist-palette: {b}", file=sys.stderr)
sys.exit(1)
print(f"verify-atarist-palette: {build} build on {machine} OK - {writes} palette writes, all 16 "
f"registers exactly the {bits} encoding of the 8 band palettes")
PY
done
if [ "$fails" -gt 0 ]; then
echo "verify-atarist-palette: FAIL" >&2
exit 1
fi
echo "verify-atarist-palette: PASS"