#!/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-x68000-input.sh - Headless typed-character acceptance gate for # the X68000 port. Boots Human68k under the patched MAME with KEYS.X in # AUTOEXEC.BAT, waits for the example's A5A5 sentinel to appear on the # screen, types the RetroNet definition-of-done string on the emulated # keyboard, and reads the verification scanline back out of text VRAM. # KEYS encodes every character it received from jlInputGetChar as nibble # pixel pairs, so the row must equal the typed ASCII exactly. # # Then the named keys the X68000 gives a second key for (src/x68000/hal.c's # kKeyAlias): the keypad's 8/2/4/6 as the arrows, its ENTER as Return, and # OPT.2 as Alt, each held alone and read back from KEYS's cells, and the # real Up and the keypad's 8 held together, then either let go, which must # leave Up down. # # Text-plane geometry: four 1bpp planes 0x20000 apart at 0xE00000, 128 # bytes per row, leftmost pixel in bit 7; the 320x200 stage is centred at # origin (96, 156). Verification row = stage y 198 -> screen y 354. # # bash scripts/verify-x68000-input.sh set -uo pipefail repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) source "$repo/scripts/lib/joeyEnv.sh" joeyNeed MAME mame joeyNeed X68K_DIR x68kFirmware joeyNeedFile "$X68K_DIR/roms/x68000/cgrom.dat" x68kCgrom source "$repo/scripts/lib/x68kDisk.sh" WALL="${X68K_INPUT_WALL:-900}" target="192.168.1.10:6510" [ -f "$X68K_SYSTEM_DISK" ] || { echo "verify-x68000-input: missing $X68K_SYSTEM_DISK" >&2; exit 2; } [ -f "$repo/build/x68000/bin/KEYS.X" ] || { echo "verify-x68000-input: build KEYS.X first (make -f make/x68000.mk)" >&2; exit 2; } work=$(mktemp -d -t joey-x68input.XXXXXX) trap 'rm -rf "$work"' EXIT x68kBootDisk "$work/boot.xdf" 'A:\KEYS.X' || exit 1 x68kXdf add "$work/boot.xdf" "$repo/build/x68000/bin/KEYS.X" KEYS.X >/dev/null || exit 1 cat > "$work/input.lua" < its 4-bit colour index. The port presents to the -- TEXT plane (src/x68000/hal.c X68K_PRESENT_TEXT): four bit planes 128 KB -- apart at 0xE00000, 128 bytes per row, leftmost pixel in bit 7, the stage -- centred at (96, 156). (Before 2026-09-24 it drew the graphics plane at -- 0xC00000, which this gate used to read.) -- The text plane holds two pages, and the port flips between them with the -- text Y scroll (CRTC R11, which reads back): rows are counted from the page -- on screen. local function shownRow() return mem:read_u16(0xE80016) & 0x3FF end local function textPix(px, y) local sx = 96 + px local addr = 0xE00000 + (156 + shownRow() + y) * 128 + (sx >> 3) local bit = 7 - (sx & 7) local v = 0 for p = 0, 3 do v = v | (((mem:read_u8(addr + p * 0x20000) >> bit) & 1) << p) end return v end local function pix(px) return textPix(px, 198) end local function findField(name) for _, port in pairs(manager.machine.ioport.ports) do if port.fields[name] then return port.fields[name] end end return nil end local function hold(names) local held = {} for _, name in ipairs(names) do held[name] = true end for _, name in ipairs(FIELDS) do fields[name]:set_value(held[name] and 1 or 0) end end local function cells() local lit = {} for i, x in ipairs(CELL_X) do lit[#lit + 1] = textPix(x, CELL_Y) == LIT and "1" or "0" end return table.concat(lit) end local function report() local bytes = {} for i = 0, 39 do bytes[#bytes + 1] = string.format("%02X", (pix(2 * i) << 4) | pix(2 * i + 1)) end io.write(string.format("VERIFY-X68K-INPUT frame=%d typedAt=%d row=%s\n", frame, typedAt, table.concat(bytes))) io.flush() end emu.register_frame_done(function() frame = frame + 1 if done then return end if typedAt == 0 and frame > 600 and frame % 100 == 0 then -- KEYS is up once initialPaint stamped the A5A5 sentinel. if pix(0) == 0xA and pix(1) == 0x5 and pix(2) == 0xA and pix(3) == 0x5 then typedAt = frame + 60 end end if typedAt ~= 0 and frame == typedAt then nat:post("$target") end if typedAt ~= 0 and keysAt == 0 and frame >= typedAt + 3000 then report() for _, name in ipairs(FIELDS) do fields[name] = findField(name) if not fields[name] then io.write("VERIFY-X68K-KEY missing field " .. name .. "\n") io.flush() done = true manager.machine:exit() return end end keysAt = frame end if keysAt ~= 0 then local step = (frame - keysAt) // STEP_FRAMES + 1 local into = (frame - keysAt) % STEP_FRAMES if step > #STEPS then done = true manager.machine:exit() return end if into == 0 then hold(STEPS[step][2]) elseif into == STEP_FRAMES - 1 then io.write(string.format("VERIFY-X68K-KEY %s|%s|%s\n", STEPS[step][1], cells(), STEPS[step][3])) io.flush() end end if frame > 30000 then done = true io.write("VERIFY-X68K-INPUT timeout: sentinel never appeared\n") io.flush() manager.machine:exit() end end) LUA # Run from the work dir so MAME's cfg/ and nvram/ droppings land there # instead of the caller's cwd. cd "$work" out=$(timeout -s KILL "$WALL" "$MAME" x68000 -bios ipl10 \ -rompath "$X68K_DIR/roms" -flop1 "$work/boot.xdf" \ -video none -sound none -nothrottle \ -autoboot_script "$work/input.lua" &1) || true line=$(echo "$out" | grep -E '^VERIFY-X68K-INPUT ' | tail -1) echo "$line" if [ -z "$line" ] || echo "$line" | grep -q timeout; then echo "verify-x68000-input: FAIL - KEYS never came up" >&2 echo "$out" | tail -10 >&2 exit 1 fi row=$(echo "$line" | sed -E 's/.*row=([0-9A-Fa-f]+).*/\1/') expect="A5A5" expect+=$(printf '%02X' ${#target}) expect+="00" expect+=$(printf '%s' "$target" | od -A n -t x1 | tr -d ' \n' | tr 'a-f' 'A-F') got=${row:0:${#expect}} if [ "$got" != "$expect" ]; then echo "verify-x68000-input: FAIL" >&2 echo " expected row prefix: $expect" >&2 echo " got: $got" >&2 exit 1 fi if echo "$out" | grep -q '^VERIFY-X68K-KEY missing'; then echo "verify-x68000-input: FAIL - $(echo "$out" | grep '^VERIFY-X68K-KEY missing' | sed 's/^VERIFY-X68K-KEY //')" >&2 exit 1 fi keyFails="" keySteps=0 while IFS='|' read -r name lit want; do keySteps=$((keySteps + 1)) echo "verify-x68000-input: $name: Return/Alt/Up/Down/Left/Right $lit" [ "$lit" = "$want" ] || keyFails="${keyFails:+$keyFails; }$name: lit $lit, want $want" done < <(echo "$out" | grep -E '^VERIFY-X68K-KEY ' | sed 's/^VERIFY-X68K-KEY //') if [ "$keySteps" = 0 ]; then echo "verify-x68000-input: FAIL - the named-key steps did not run" >&2 exit 1 fi if [ -n "$keyFails" ]; then echo "verify-x68000-input: FAIL - $keyFails" >&2 exit 1 fi echo "verify-x68000-input: PASS (typed \"$target\", all ${#target} chars arrived via jlInputGetChar; $keySteps named-key steps: the keypad's arrows and ENTER, OPT.2 as Alt, Up held with its alias)"