joeylib2/scripts/verify-iigs-tilebank.sh
2026-10-19 15:46:47 -05:00

201 lines
7.9 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-iigs-tilebank.sh - Tile-bank gate for the Apple IIgs: proves that
# assetbake's iigs encoder (chunky 4bpp, high nibble = left pixel) writes
# exactly the layout the IIgs tile paste reads, and that the IIgs build
# refuses a bank baked for any other target.
#
# Builds a one-example JOEYLIB disk (TILEBANK, examples/tilebank, with the
# DATA/tilebank make/iigs.mk staged from the host gate's fixture), boots GS/OS
# under headless MAME and launches TILEBANK from the Finder (verify-iigs-
# save.sh's keystroke timeline). The program loads its own bake through
# jlTileBankLoadToSurface and through jlTileBankLoad + jlTilePaste, checks
# every pixel of both against the PNG, hashes both, tries every other
# target's bake, writes one TILEBANK line to JOEYLOG.TXT (jlLogFlush: one
# open/write/close) and only then shows its verdict lamp. The Lua below waits
# for the lamp in the Super Hi-Res framebuffer and stops MAME; the line,
# extracted from the work disk with cadius, must say PASS with both hashes
# equal to the host gate's (scripts/check-tilebank.sh, run here for the
# value) and every other target refused, and the lamp must be the PASS
# color.
#
# SHR geometry: 160 bytes per row at $E12000, high nibble = left pixel; the
# stage is the whole 320x200 screen. The lamp is examples/tilebank's: 64x64
# at (128, 68), color 1 for PASS and 2 for FAIL, with FILL_COLOR (9) either
# side of it on its rows.
#
# Usage: scripts/verify-iigs-tilebank.sh
# (make -f make/iigs.mk iigs-verify-tilebank builds what it needs first)
# Env: TILEBANK_DATA_DIR stage this DATA/tilebank instead (negative controls)
# Requires: ./install.sh install iigs; MAME with the apple2gs ROM and the GS/OS
# system disk; a host cc; build/iigs/bin/TILEBANK and its
# DATA/tilebank (staged in build/iigs/data/TILEBANK/DATA).
set -uo pipefail
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
source "$repo/scripts/lib/joeyEnv.sh"
source "$repo/scripts/lib/tileBankGate.sh"
joeyNeed LLVM816_ROOT toolchain:iigs
joeyNeed CADIUS cadius
joeyNeed MAME mame
joeyNeed IIGS_ROMPATH iigsFirmware
joeyNeed GSOS_SYSTEM_DISK iigsFirmware
binDir=$repo/build/iigs/bin
dataDir=$(tileBankData "$repo/build/iigs/data/TILEBANK")
wall=900 # hard cap on MAME, whatever happens to this script
frames=24000 # emulated frames before the Lua gives up on the lamp
tileBankNeedBuild "$binDir/TILEBANK" "$dataDir" "make -f make/iigs.mk iigs-verify-tilebank"
tileBankReference
work=$(mktemp -d -t joeylib-iigs-tilebank.XXXXXX)
trap 'rm -rf "$work"' EXIT
# make-iigs-disk.sh packs BINDIR's TILEBANK and its DATA, staged in
# BINDIR/../data/TILEBANK/DATA, so a scratch BINDIR carries whichever
# DATA/tilebank this run stages.
mkdir -p "$work/bin" "$work/data/TILEBANK/DATA" "$work/log"
cp "$binDir/TILEBANK" "$work/bin/"
cp -r "$dataDir" "$work/data/TILEBANK/DATA/tilebank"
BINDIR="$work/bin" JOEY_DISK_EXAMPLES="TILEBANK" "$repo/scripts/make-iigs-disk.sh" "$work/joey.2mg" > /dev/null || exit 1
if ! "$CADIUS" CATALOG "$work/joey.2mg" 2> /dev/null | grep -qE '^ +OWN\.TBK +BIN'; then
echo "verify-iigs-tilebank: FAIL - the disk has no DATA/TILEBANK/OWN.TBK" >&2
exit 1
fi
cp "$GSOS_SYSTEM_DISK" "$work/boot.po"
cat > "$work/tilebank.lua" <<LUA
local mem = manager.machine.devices[":maincpu"].spaces["program"]
local nat = manager.machine.natkeyboard
local FRAMES = $frames
local FIRST_LOOK = 3800 -- the Finder launch below is done by then
local SAMPLE_EVERY = 10 -- frames between lamp looks
local LAMP_STABLE = 3 -- looks in a row that must agree
local EXIT_SETTLE = 120 -- let the program settle before MAME writes the disk back
local LAMP_X, LAMP_Y, LAMP_W, LAMP_H = 128, 68, 64, 64
local PASS_COLOR, FAIL_COLOR, FILL_COLOR = 1, 2, 9
local MARGIN = 4 -- inside probes keep off the lamp's edge
local BESIDE = 8 -- outside probes sit this far off it
local frame = 0
local idx = 1
local seen = 0
local lamp = 0
local exitAt = 0
local done = false
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 keyCmd = 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 stagePixel(x, y)
local b = mem:read_u8(0xE12000 + y * 160 + (x >> 1))
if (x & 1) == 0 then return (b >> 4) & 0x0F else return b & 0x0F end
end
-- The lamp color when the whole lamp shows one verdict color with
-- FILL_COLOR on its rows either side, else 0.
local function lampColor()
local left = LAMP_X + MARGIN
local right = LAMP_X + LAMP_W - 1 - MARGIN
local top = LAMP_Y + MARGIN
local bottom = LAMP_Y + LAMP_H - 1 - MARGIN
local midY = LAMP_Y + LAMP_H // 2
local c = stagePixel(left, top)
if c ~= PASS_COLOR and c ~= FAIL_COLOR then
return 0
end
for _, p in ipairs({ {right, top}, {left, bottom}, {right, bottom}, {LAMP_X + LAMP_W // 2, midY} }) do
if stagePixel(p[1], p[2]) ~= c then
return 0
end
end
if stagePixel(LAMP_X - BESIDE, midY) ~= FILL_COLOR or stagePixel(LAMP_X + LAMP_W - 1 + BESIDE, midY) ~= FILL_COLOR then
return 0
end
return c
end
-- Select the JOEYLIB volume, open it, select TILEBANK, open it.
local steps = {
{3000, function() nat:post("J") end},
{3120, function() press(keyCmd) end},
{3126, function() nat:post("o") end},
{3180, function() release(keyCmd) end},
{3540, function() nat:post("TILEBANK") end},
{3660, function() press(keyCmd) end},
{3666, function() nat:post("o") end},
{3720, function() release(keyCmd) end},
}
emu.register_frame_done(function()
frame = frame + 1
if done then return end
while idx <= #steps and frame >= steps[idx][1] do
steps[idx][2]()
idx = idx + 1
end
if exitAt == 0 then
if frame >= FIRST_LOOK and frame % SAMPLE_EVERY == 0 then
local c = lampColor()
if c ~= 0 and c == lamp then
seen = seen + 1
else
seen = (c ~= 0) and 1 or 0
end
lamp = c
if seen >= LAMP_STABLE then
io.write(string.format("TILEBANK-IIGS lamp=%s f%d\n", (lamp == PASS_COLOR) and "PASS" or "FAIL", frame))
io.flush()
exitAt = frame + EXIT_SETTLE
end
end
elseif frame >= exitAt then
done = true
manager.machine:exit()
end
if frame > FRAMES then
done = true
io.write("TILEBANK-IIGS no-lamp f"..frame.."\n")
io.flush()
manager.machine:exit()
end
end)
LUA
# From the work dir, so MAME's cfg/ and nvram/ never persist between runs.
out=$(cd "$work" && QT_QPA_PLATFORM=offscreen SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \
timeout -s KILL "$wall" "$MAME" apple2gs \
-rompath "$IIGS_ROMPATH" \
-flop3 "$work/boot.po" -flop4 "$work/joey.2mg" \
-video none -sound none -nothrottle \
-autoboot_script "$work/tilebank.lua" < /dev/null 2>&1 | grep '^TILEBANK-IIGS')
echo "${out:-TILEBANK-IIGS: no report from the Lua (MAME died, or hit the ${wall}s cap)}"
lampOk=1
if ! grep -q 'lamp=PASS' <<< "$out"; then
lampOk=0
fi
# cadius names the extracted file with its ProDOS type appended.
"$CADIUS" EXTRACTFILE "$work/joey.2mg" "/JOEYLIB/JOEYLOG.TXT" "$work/log/" > /dev/null 2>&1
log=$(find "$work/log" -maxdepth 1 -iname 'joeylog.txt*' -print -quit)
if [ -z "$log" ]; then
echo "verify-iigs-tilebank: FAIL - no JOEYLOG.TXT on the work disk (the program never flushed its log)" >&2
exit 1
fi
tileBankJudge "$log" || exit 1
if [ "$lampOk" -eq 0 ]; then
echo "verify-iigs-tilebank: FAIL - the on-screen lamp did not show PASS" >&2
exit 1
fi