#!/usr/bin/env bash
# verify-iigs.sh - Headless, deterministic visual-verification gate for the
# IIgs port. Boots GS/OS under MAME, drives the Finder to launch one example
# off the JOEYLIB data disk, then reads the Super Hi-Res framebuffer directly
# out of emulated memory ($E1/2000..$E1/9CFF) and reports how many distinct
# 4-bit pixel values were drawn plus a checksum. A blank/Finder screen yields
# a tiny distinct count; a real rendered demo (pixels, lines, circles, flood
# fills) lights up many of the 16 colors.
#
# Why memory reads instead of PNG snapshots: MAME's video:snapshot() needs a
# real render target, which a headless `-video none` run does not have. Reading
# the SHR bytes the asm actually wrote is render-target-independent and exact.
#
# Usage:   scripts/verify-iigs.sh [example] [minDistinct]
#   example      lowercase name (default: draw). Must be on joey.2mg (run
#                `make iigs-disk` first). The volume is JOEYLIB.
#   minDistinct  PASS threshold for distinct nibble values. If omitted, a
#                per-example default is used: 4 for most demos, but 3 for the
#                deliberately sparse ones (keys draws a mostly-monochrome grid;
#                sprite draws one small ball on a black background), which
#                legitimately render only ~3 distinct colors. An explicit
#                second arg always overrides.
#
# Requires: toolchains/env.sh sourced; build/iigs/bin/joey.2mg built.
set -euo pipefail

repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
example=${1:-draw}
NAME=${example^^}
if [ -n "${2:-}" ]; then
    minDistinct=$2
else
    case "$example" in
        keys|sprite) minDistinct=3 ;;
        *)           minDistinct=4 ;;
    esac
fi

sys_disk=$repo/toolchains/emulators/support/gsos-system.po
data_disk=$repo/build/iigs/bin/joey.2mg
rompath="${MAME_ROMPATH:-$HOME/.mame/roms}"
readFrame="${MAME_READ_FRAME:-9000}"

for f in "$sys_disk" "$data_disk"; do
    [ -f "$f" ] || { echo "verify-iigs: missing $f (run 'make iigs-disk')" >&2; exit 2; }
done

work=$(mktemp -d -t joeylib-verify.XXXXXX)
trap 'rm -rf "$work"' EXIT
cp "$sys_disk"  "$work/boot.po"
cp "$data_disk" "$work/joey.2mg"

# Finder keystroke timeline (frame counts calibrated against gsos-system.po):
# select JOEYLIB volume (J), Cmd-O to open it, type the program name, Cmd-O to
# launch. Then at readFrame, sum the SHR framebuffer and report. No -debug: we
# drive input with natkeyboard and read memory via the program space directly,
# and exit cleanly from Lua (so MAME never lingers).
cat > "$work/verify.lua" <<LUA
local cpu = manager.machine.devices[":maincpu"]
local mem = cpu.spaces["program"]
local nat = manager.machine.natkeyboard
local frame = 0
local idx = 1

local function field(port, name)
    local p = manager.machine.ioport.ports[port]
    if p == nil then return nil end
    return p.fields[name]
end
local key_cmd = field(":macadb:KEY3", "Command / Open Apple")
local function press(f)   if f then f:set_value(1) end end
local function release(f) if f then f:set_value(0) end end

local function report()
    -- SHR pixels: \$E1/2000 .. \$E1/9CFF = 32000 bytes, 2 pixels/byte (4bpp).
    local base = 0xE12000
    local count = 32000
    local seen = {}
    local distinct = 0
    local nonzero = 0
    local sum = 0
    for i = 0, count - 1 do
        local b = mem:read_u8(base + i)
        if b ~= 0 then nonzero = nonzero + 1 end
        sum = (sum + b) & 0xFFFFFF
        local hi = (b >> 4) & 0x0F
        local lo = b & 0x0F
        if not seen[hi] then seen[hi] = true; distinct = distinct + 1 end
        if not seen[lo] then seen[lo] = true; distinct = distinct + 1 end
    end
    -- Launch witnesses, read from the display state ABOVE the pixel block.
    -- scb0: jlScbSet/jlScbSetRange reject any palette index >= 16, so a
    -- JoeyLib SCB byte is always 0x00-0x0F, while the Finder leaves 640-mode
    -- bytes (bit 7 set, 0x80+). The dirty flags start true, so the very first
    -- jlpPresent uploads the SCB -- scb0 < 0x10 therefore proves a JoeyLib
    -- app took the screen. slamPark: the PEI slam parks 2*runMax+1 (always
    -- ODD, 1..159) at \$E1:9DFE, which the Finder's 0x80 fill never is.
    -- palSum: folds all 16 palettes so a wrong/stale palette block is visible
    -- at all (the pixel scan stops at \$9CFF and never reads them).
    local scb0 = mem:read_u8(0xE19D00)
    local park = mem:read_u8(0xE19DFE)
    local palsum = 0
    for i = 0, 511 do
        palsum = (palsum + mem:read_u8(0xE19E00 + i)) & 0xFFFFFF
    end
    io.write(string.format("VERIFY-IIGS name=$NAME frame=%d distinctNibbles=%d nonZeroBytes=%d checksum=%06X scb0=%02X slamPark=%02X palSum=%06X\n",
                           frame, distinct, nonzero, sum, scb0, park, palsum))
    io.flush()
end

local steps = {
    {3000, function() nat:post("J") end},
    {3120, function() press(key_cmd) end},
    {3126, function() nat:post("o") end},
    {3180, function() release(key_cmd) end},
    {3540, function() nat:post("$NAME") end},
    {3660, function() press(key_cmd) end},
    {3666, function() nat:post("o") end},
    {3720, function() release(key_cmd) end},
    {$readFrame, function() report(); manager.machine:exit() end},
}

emu.register_frame_done(function()
    frame = frame + 1
    while idx <= #steps and frame >= steps[idx][1] do
        steps[idx][2]()
        idx = idx + 1
    end
end)
LUA

cd "$work"
out=$(QT_QPA_PLATFORM=offscreen SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \
    timeout 300 mame apple2gs \
        -rompath "$rompath" \
        -flop3 "$work/boot.po" -flop4 "$work/joey.2mg" \
        -video none -sound none -nothrottle \
        -autoboot_script "$work/verify.lua" </dev/null 2>&1) || true

line=$(echo "$out" | grep -E '^VERIFY-IIGS ' | tail -1)
echo "$line"
if [ -z "$line" ]; then
    echo "verify-iigs: FAIL - no framebuffer report (boot/launch failed)" >&2
    echo "$out" | tail -15 >&2
    exit 1
fi
# Launch gate FIRST: without it this script grades whatever is on screen, and
# the GS/OS Finder desktop alone lights up ~9-10 distinct nibbles -- i.e. it
# PASSED for an example that never launched (control: `verify-iigs.sh
# nosuchapp` -> "distinctNibbles=9 ... PASS", scb0=80). Every IIgs visual claim
# made through this script was unsound until this check existed, and a memory
# dump taken during such a run reads the Finder's own SCB/palette block rather
# than the app's.
scb0=$(echo "$line" | sed -E 's/.*scb0=([0-9A-Fa-f]+).*/\1/')
if [ $((16#$scb0)) -ge 16 ]; then
    echo "verify-iigs: FAIL ($NAME never launched - SHR SCB[0]=0x$scb0 still has bit 7 set, i.e. the Finder's 640-mode fill; a JoeyLib SCB byte is always 0x00-0x0F and the first present uploads it)" >&2
    exit 1
fi
distinct=$(echo "$line" | sed -E 's/.*distinctNibbles=([0-9]+).*/\1/')
if [ "$distinct" -ge "$minDistinct" ]; then
    echo "verify-iigs: PASS ($NAME rendered, distinctNibbles=$distinct >= $minDistinct)"
else
    echo "verify-iigs: FAIL ($NAME distinctNibbles=$distinct < $minDistinct - screen looks blank)" >&2
    exit 1
fi
