#!/usr/bin/env bash
# verify-x68000-golden.sh - X68000 golden-hash gate.
#
# Builds UBER with a 1-frame measurement window (the timings are meaningless at
# that setting; this run is for the HASHES), stages it on a Human68k image,
# runs it headless under the patched MAME, extracts joeylog.txt back off the
# image with xdftool, and diffs the hashes against the Apple IIgs reference.
#
# Takes a few minutes. The run EXITS ON COMPLETION, not on a frame budget: UBER
# ends by clearing the whole stage to colour index 2 and presenting it before it
# blocks in jlWaitForAnyKey, so the Lua below watches GVRAM for that screen.
# (It used to burn a flat 300,000 frames -- ~90 minutes of emulated time -- a
# budget sized back when this port still drew through the slow generic renderer.
# The actual work is a Human68k boot plus well under 10,000 frames.)
# X68K_GOLDEN_FRAMES is now only a backstop: reaching it means the done-screen
# was never detected, which is a FAILURE, not a normal exit.
#
#   X68K_SCRATCH=<dir with x68mame/> bash scripts/verify-x68000-golden.sh
set -uo pipefail

repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
SP="${X68K_SCRATCH:?set X68K_SCRATCH to a work dir containing x68mame/}"
MAME="$repo/toolchains/cache/mame-mame0264/x68k"
TEMPLATE="$SP/x68mame/HUMAN302.XDF"
GOLDEN="${X68K_GOLDEN:-$repo/tests/goldens/uber/iigs.txt}"
FRAMES="${X68K_GOLDEN_FRAMES:-60000}"
WALL="${X68K_GOLDEN_WALL:-7200}"

export PATH="$repo/toolchains/x68000/m68k-xelf/bin:$PATH"
work=$(mktemp -d -t joey-x68gold.XXXXXX)
trap 'rm -rf "$work"' EXIT

# -s (strip) is REQUIRED: the unstripped binary is ~247 KB and does not fit
# alongside Human68k on a 1232 KB floppy (232 KB free).
m68k-xelf-gcc -s -O2 -m68000 -fomit-frame-pointer \
    -DJOEYLIB_PLATFORM_X68000 -DUBER_FRAMES=1u \
    -I"$repo/include" -I"$repo/src/core" -I"$repo/src/x68000" \
    -I"$repo/toolchains/audio/libxmp-lite/include" \
    "$repo/examples/uber/uber.c" \
    "$repo/build/x68000/lib/libjoey.a" "$repo/build/x68000/lib/libxmplite.a" -lm \
    -o "$work/UBER.X" || exit 1

# TWO DISKS. UBER.X is ~250 KB and Human68k needs ~90 KB, which no longer fit
# together on a 1232 KB floppy at all -- so the boot disk carries only Human68k
# and AUTOEXEC, while the binary AND joeylog.txt live on the second disk on B:.
# AUTOEXEC switches to B: before launching so the log lands there too.
printf 'B:\r\nB:\\UBER.X\r\n\x1a' > "$work/AUTOEXEC.BAT"
cp "$TEMPLATE" "$work/gold.xdf"
# Data disk = the template with every file removed (keeps the format).
cp "$TEMPLATE" "$work/data.xdf"
for f in HUMAN.SYS CONFIG.SYS KEY.SYS USKCG.SYS BEEP.SYS STARTUP.ENV COMMAND.X AUTOEXEC.BAT; do
    python3 "$repo/tools/xdftool.py" delete "$work/data.xdf" "$f" >/dev/null 2>&1
done
# Trim the boot disk of anything not needed to boot and run one .X.
for f in USKCG.SYS BEEP.SYS KEY.SYS STARTUP.ENV; do
    python3 "$repo/tools/xdftool.py" delete "$work/gold.xdf" "$f" >/dev/null 2>&1
done
python3 "$repo/tools/xdftool.py" add "$work/data.xdf" "$work/UBER.X" UBER.X >/dev/null || exit 1
python3 "$repo/tools/xdftool.py" add "$work/gold.xdf" "$work/AUTOEXEC.BAT" AUTOEXEC.BAT >/dev/null

# Completion-driven exit. UBER's last act before jlWaitForAnyKey is
# jlSurfaceClear(gStage, 2) + jlStagePresent, so an all-colour-2 stage in GVRAM
# is the "hashes are computed and flushed" signal. The benchmark's own
# jlSurfaceClear/jlStagePresent ops can paint that colour for an instant, so the
# reading only counts once it has held for GREEN_STABLE frames -- the finished
# screen never changes again, a mid-benchmark frame never lasts.
#
# jlLogFlush only fflush()es; the directory entry is finalised by the atexit
# fclose. So the keypress that releases jlWaitForAnyKey still has to be posted,
# and MAME must not be killed until UBER has returned to Human68k -- hence the
# repeated posts and the EXIT_SETTLE wait before exiting.
cat > "$work/gold.lua" <<LUA
local mem = manager.machine.devices[":maincpu"].spaces["program"]
local frame     = 0
local greenRun  = 0
local exitAt    = 0
local done      = false

local FRAMES       = $FRAMES
local GREEN_STABLE = 600    -- ~11 s of emulated time held steady
local EXIT_SETTLE  = 900    -- keypress -> fclose -> back at the Human68k prompt

-- crtmod 13, 256-colour graphics plane: one 16-bit word per pixel at \$C00000,
-- 512 words per row, the 320x200 stage centred at origin (96, 156).
local function stagePixel(px, py)
    return mem:read_u8(0xC00000 + ((156 + py) * 512 + 96 + px) * 2 + 1)
end

local kProbes = { {4,4}, {160,4}, {315,4}, {4,100}, {160,100}, {315,100},
                  {4,196}, {160,196}, {315,196}, {80,50}, {240,150}, {200,80} }

local function screenIsDone()
    for _, p in ipairs(kProbes) do
        if stagePixel(p[1], p[2]) ~= 2 then
            return false
        end
    end
    return true
end

emu.register_frame_done(function()
    frame = frame + 1
    if done then return end
    if frame % 2000 == 0 then io.write("GOLD f"..frame.."\n"); io.flush() end

    if exitAt == 0 then
        if screenIsDone() then
            greenRun = greenRun + 1
            if greenRun >= GREEN_STABLE then
                io.write("GOLD done-screen f"..frame.."\n"); io.flush()
                exitAt = frame + EXIT_SETTLE
            end
        else
            greenRun = 0
        end
    else
        -- Release jlWaitForAnyKey; extra presses land harmlessly at the prompt.
        if frame % 60 == 0 then manager.machine.natkeyboard:post(" ") end
        if frame >= exitAt then
            done = true
            io.write("GOLD exit f"..frame.."\n"); io.flush()
            manager.machine:exit()
        end
    end

    -- Backstop only: reaching this means the done-screen never appeared.
    if frame > FRAMES then
        done = true
        io.write("GOLD budget-exhausted f"..frame.."\n"); io.flush()
        manager.machine:exit()
    end
end)
LUA

runLog=$(timeout -s KILL "$WALL" "$MAME" x68000 -bios ipl10 \
    -rompath "$SP/x68mame/roms" -flop1 "$work/gold.xdf" -flop2 "$work/data.xdf" \
    -video none -sound none -nothrottle \
    -autoboot_script "$work/gold.lua" </dev/null 2>&1 | grep '^GOLD')
echo "$runLog"

# The backstop is not a normal exit: it means UBER never reached its done-screen,
# so whatever log is on the disk (if any) is from an unfinished run.
if echo "$runLog" | grep -q 'budget-exhausted'; then
    echo "verify-x68000-golden: FAIL - UBER's done-screen never appeared within $FRAMES frames" >&2
    echo "  (raise X68K_GOLDEN_FRAMES if UBER legitimately got slower; otherwise the run died early)" >&2
    exit 1
fi
if ! echo "$runLog" | grep -q 'done-screen'; then
    echo "verify-x68000-golden: FAIL - MAME stopped before UBER finished (wall timeout ${WALL}s?)" >&2
    exit 1
fi

python3 "$repo/tools/xdftool.py" extract "$work/data.xdf" joeylog.txt "$work/x68.txt" || {
    echo "verify-x68000-golden: FAIL - no joeylog.txt (run did not reach jlLogFlush)" >&2
    exit 1
}
tr -d '\r' < "$work/x68.txt" > "$work/x68.clean"
cp "$work/x68.clean" "$repo/build/x68000/joeylog.txt"
"$repo/tools/diff-uber-hashes" "$GOLDEN" "$work/x68.clean"
