#!/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 colors 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 color. # # 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 color # 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" awk -v machine="$machine" -v build="$build" ' # One 4-bit channel as that build writes the shifter nibble. function channel(v) { return build == "atarist" ? rshift(v, 1) : or(rshift(v, 1), lshift(and(v, 1), 3)) } # A set of color words as Python prints a sorted list of their hex. function hexList(set, count, names, i, n, out) { n = 0 for (i = 0; i < count; i++) { names[++n] = sprintf("0x%x", set[i]) } asort(names) out = "" for (i = 1; i <= n; i++) { out = out (i > 1 ? ", " : "") "\x27" names[i] "\x27" } return "[" out "]" } BEGIN { # PATTERN'"'"'s eight palettes: which of red, green, and blue carry # the gradient. split("111 100 110 010 011 001 101 111", gradients, " ") } # write col addr=ff825e col=70 video_cyc_w=55892 ... pc=1f00e match($0, /^write col addr=ff82([45][0-9a-f]) col=([0-9a-f]+) /, m) { register = int((strtonum("0x" m[1]) - 64) / 2) value = strtonum("0x" m[2]) if (!((register, value) in seen)) { seen[register, value] = 1 got[register, seenCount[register]++] = value } writes++ } END { bits = build == "atarist" ? "3-bit" : "4-bit" if (writes == 0) { printf "verify-atarist-palette: %s build on %s FAIL - PATTERN never wrote the palette\n", build, machine > "/dev/stderr" exit 1 } badCount = 0 for (i = 0; i < 16; i++) { delete want delete wantList wantCount = 0 v = i ? channel(i) : 0 for (g = 1; g <= 8; g++) { word = (substr(gradients[g], 1, 1) == "1" ? lshift(v, 8) : 0) + (substr(gradients[g], 2, 1) == "1" ? lshift(v, 4) : 0) + (substr(gradients[g], 3, 1) == "1" ? v : 0) if (!(word in want)) { want[word] = 1 wantList[wantCount++] = word } } same = wantCount == seenCount[i] for (word in want) { same = same && ((i, word + 0) in seen) } if (!same) { delete gotList for (k = 0; k < seenCount[i]; k++) { gotList[k] = got[i, k] } bad[++badCount] = sprintf("register %d: wrote %s, want %s", i, hexList(gotList, seenCount[i]), hexList(wantList, wantCount)) } } if (badCount > 0) { printf "verify-atarist-palette: %s build on %s FAIL - not the %s encoding of PATTERN'"'"'s palettes:\n", build, machine, bits > "/dev/stderr" for (b = 1; b <= badCount && b <= 4; b++) { printf "verify-atarist-palette: %s\n", bad[b] > "/dev/stderr" } exit 1 } printf "verify-atarist-palette: %s build on %s OK - %d palette writes, all 16 registers exactly the %s encoding of the 8 band palettes\n", build, machine, writes, bits }' "$work/$machine.log" || fails=$((fails + 1)) done if [ "$fails" -gt 0 ]; then echo "verify-atarist-palette: FAIL" >&2 exit 1 fi echo "verify-atarist-palette: PASS"