Skip to content

Record M4.3 Falcon 040 qualification #125

Record M4.3 Falcon 040 qualification

Record M4.3 Falcon 040 qualification #125

name: steem-sse integration probe
on:
workflow_dispatch:
push:
paths:
- '.github/workflows/steem-sse-probe.yml'
- 'docs/M3_2_STEEM_SSE.md'
- 'ROADMAP.md'
jobs:
linux-startup:
runs-on: ubuntu-24.04
steps:
- name: Fetch pinned Steem SSE Linux binary
run: |
set -eux
git clone --depth 1 --branch ubuntu https://github.com/gsidh761/SteamSSELinux.git steem-linux
cd steem-linux
git rev-parse HEAD | tee ../steem-sse-revision.txt
test -s bin/xsteem64
chmod +x bin/xsteem64
file bin/xsteem64
sha256sum bin/xsteem64 | tee ../steem-sse-sha256.txt
- name: Fetch canonical atari-dev runtime probe
env:
GH_TOKEN: ${{ github.token }}
run: |
set -eux
mkdir -p artifacts
gh api repos/Ploos-AS/atari-dev/actions/artifacts/10600359653/zip > /tmp/atari-dev.zip
unzip -q /tmp/atari-dev.zip -d artifacts
probe="$(find artifacts -type f -name MINIMAL.PRG | head -n 1)"
test -n "$probe"
if [ "$probe" != "artifacts/MINIMAL.PRG" ]; then
cp "$probe" artifacts/MINIMAL.PRG
fi
test -s artifacts/MINIMAL.PRG
# Build a deterministic 720 KiB ST floppy with MINIMAL.PRG in AUTO.
# This avoids GEM desktop-coordinate assumptions entirely: EmuTOS runs
# AUTO programs during boot, while Steem's documented .st command-line
# option inserts the image and starts emulation.
sudo apt-get update -qq
sudo apt-get install -y -qq mtools
rm -f steem-linux/bin/minimal-auto.st
truncate -s 720K steem-linux/bin/minimal-auto.st
mformat -i steem-linux/bin/minimal-auto.st -f 720 ::
# Mark the floppy bootable for TOS/EmuTOS. A formatted FAT image
# alone is not sufficient to guarantee AUTO processing at cold boot.
# Keep this as a normal non-executable TOS data disk. A 0x1234
# boot-sector checksum tells TOS to execute sector 0 as boot code;
# our FAT BPB is data, so forcing that checksum causes an illegal-
# instruction panic before AUTO processing.
mmd -i steem-linux/bin/minimal-auto.st ::/AUTO
# TOS AUTO boot convention requires *.PRG directly in \AUTO.
# Use a classic 8.3 name to avoid any emulator/TOS filename edge.
mcopy -i steem-linux/bin/minimal-auto.st artifacts/MINIMAL.PRG ::/AUTO/PROBE.PRG
mdir -i steem-linux/bin/minimal-auto.st ::/AUTO
mdir -i steem-linux/bin/minimal-auto.st ::/AUTO/PROBE.PRG
- name: Install runtime dependencies
run: |
sudo apt-get update
sudo apt-get install -y strace libpulse0 xvfb gdb x11-utils xdotool imagemagick xfonts-base xfonts-75dpi xfonts-100dpi
test -s steem-linux/libs/libcapsimage.so.5
mkdir -p "$RUNNER_TEMP/steem-libs"
cp steem-linux/libs/libcapsimage.so.5 "$RUNNER_TEMP/steem-libs/"
sha256sum "$RUNNER_TEMP/steem-libs/libcapsimage.so.5" | tee steem-sse-capsimage-sha256.txt
echo "LD_LIBRARY_PATH=$RUNNER_TEMP/steem-libs" >> "$GITHUB_ENV"
- name: Inspect dynamic dependencies
run: |
ldd steem-linux/bin/xsteem64 | tee steem-sse-ldd.txt
if grep -q 'not found' steem-sse-ldd.txt; then
echo 'Steem SSE still has unresolved shared libraries.' >&2
exit 1
fi
- name: Verify X11 font resources
run: |
xvfb-run -a sh -c 'xlsfonts | tee x11-fonts.txt; test "$(xlsfonts | wc -l)" -gt 0'
grep -E '(^8x13$|helvetica|lucida|palatino)' x11-fonts.txt | head -50 | tee x11-font-candidates.txt || true
if ! grep -Eq '(^8x13$|helvetica|lucida|palatino)' x11-fonts.txt; then
echo 'No Steem-compatible X11 core font candidate found.' >&2
exit 1
fi
- name: Provision canonical EmuTOS ROM
env:
EMUTOS_VERSION: '1.4'
EMUTOS_URL: https://downloads.sourceforge.net/project/emutos/emutos/1.4/emutos-256k-1.4.zip
run: |
set -eux
mkdir -p roms /tmp/emutos
curl --fail --location --show-error --silent -o /tmp/emutos.zip "$EMUTOS_URL"
unzip -q /tmp/emutos.zip -d /tmp/emutos
rom="$(find /tmp/emutos -type f -name 'etos256*.img' | head -n 1)"
test -n "$rom"
cp "$rom" roms/emutos.img
test -s roms/emutos.img
printf 'emutos_version=%s\n' "$EMUTOS_VERSION" | tee steem-sse-emutos-version.txt
- name: Configure EmuTOS boot probe
run: |
set -eux
# Provision both names before touching steem.ini. Steem SSE's
# first-run code and ROM browser use the configured .rom path, while
# the command-line probe retains the canonical downloaded .img name.
cp roms/emutos.img steem-linux/bin/roms/emutos.img
cp roms/emutos.img steem-linux/bin/roms/emutos.rom
test -s steem-linux/bin/roms/emutos.img
test -s steem-linux/bin/roms/emutos.rom
cmp -s steem-linux/bin/roms/emutos.img steem-linux/bin/roms/emutos.rom
sha256sum steem-linux/bin/roms/emutos.img steem-linux/bin/roms/emutos.rom | tee steem-sse-provisioned-rom-sha256.txt
python3 - <<'PY'
from pathlib import Path
p = Path("steem-linux/bin/steem.ini")
s = p.read_text()
lines = []
in_options = False
seen_rom = False
for line in s.splitlines():
if line.startswith("["):
in_options = line.strip() == "[Options]"
if in_options and line.startswith("NewROMFile="):
line = f"NewROMFile={Path.cwd() / 'steem-linux/bin/roms/emutos.rom'}"
seen_rom = True
lines.append(line)
if not seen_rom:
raise SystemExit("NewROMFile key missing from Steem configuration")
p.write_text("\n".join(lines) + "\n")
PY
grep -F "NewROMFile=$GITHUB_WORKSPACE/steem-linux/bin/roms/emutos.rom" steem-linux/bin/steem.ini
# Steem's active ROM is persisted separately under [Machine].
# NewROMFile alone still triggers the first-run TOS chooser.
python3 - <<'PY'
from pathlib import Path
p = Path("steem-linux/bin/steem.ini")
s = p.read_text()
rom = str(Path.cwd() / "steem-linux/bin/roms/emutos.rom")
lines = []
in_machine = False
seen = False
for line in s.splitlines():
if line.startswith("["):
in_machine = line.strip() == "[Machine]"
if in_machine and line.startswith("ROM_File="):
line = f"ROM_File={rom}"
seen = True
lines.append(line)
if not seen:
raise SystemExit("ROM_File key missing from Steem configuration")
p.write_text("\n".join(lines) + "\n")
PY
grep -F "ROM_File=$GITHUB_WORKSPACE/steem-linux/bin/roms/emutos.rom" steem-linux/bin/steem.ini
# Steem's first-run ROM chooser also scans ROM_Add_Dir. Point it at
# the directory containing only our canonical open EmuTOS image.
# Keep the traditional .img name, and also provide a .tos alias:
# Steem's ROM browser/filter may classify TOS images by extension.
python3 - <<'PY'
from pathlib import Path
p = Path("steem-linux/bin/steem.ini")
s = p.read_text()
romdir = str(Path.cwd() / "steem-linux/bin/roms")
lines = []
in_machine = False
seen = False
for line in s.splitlines():
if line.startswith("["):
in_machine = line.strip() == "[Machine]"
if in_machine and line.startswith("ROM_Add_Dir="):
line = f"ROM_Add_Dir={romdir}"
seen = True
lines.append(line)
if not seen:
raise SystemExit("ROM_Add_Dir key missing from Steem configuration")
p.write_text("\n".join(lines) + "\n")
PY
grep -F "ROM_Add_Dir=$GITHUB_WORKSPACE/steem-linux/bin/roms" steem-linux/bin/steem.ini
# Configure drive A through Steem's native INI keys. The Linux
# command-line parser treats DISK_A=... as a filename, not an option.
python3 - <<'PY'
from pathlib import Path
p = Path("steem-linux/bin/steem.ini")
disk = Path.cwd() / "steem-linux/bin/minimal-auto.st"
lines = p.read_text().splitlines()
out = []
section = ""
seen_path = seen_name = False
for line in lines:
if line.startswith("[") and line.endswith("]"):
section = line
if section == "[Disks]" and line.startswith("Disk_A_Path="):
line = f"Disk_A_Path={disk}"
seen_path = True
elif section == "[Disks]" and line.startswith("Disk_A_Name="):
line = f"Disk_A_Name={disk.name}"
seen_name = True
out.append(line)
if not (seen_path and seen_name):
raise SystemExit("Steem [Disks] drive A keys missing")
p.write_text("\n".join(out) + "\n")
PY
grep -F "Disk_A_Path=$GITHUB_WORKSPACE/steem-linux/bin/minimal-auto.st" steem-linux/bin/steem.ini
grep -F "Disk_A_Name=minimal-auto.st" steem-linux/bin/steem.ini
python3 - <<'PY'
from pathlib import Path
p = Path("steem-linux/bin/steem.ini")
lines = p.read_text().splitlines()
out = []
section = ""
seen = False
for line in lines:
if line.startswith("[") and line.endswith("]"):
section = line
if section == "[Update]" and line.startswith("LatestVersion="):
line = "LatestVersion=999.0.0"
seen = True
out.append(line)
if not seen:
pos = next((i + 1 for i, line in enumerate(out) if line == "[Update]"), None)
if pos is None:
raise SystemExit("[Update] section missing")
out.insert(pos, "LatestVersion=999.0.0")
p.write_text("\n".join(out) + "\n")
PY
grep -F "LatestVersion=999.0.0" steem-linux/bin/steem.ini
sha256sum roms/emutos.img | tee steem-sse-emutos-sha256.txt
python3 - <<'PY'
from pathlib import Path
data = Path("roms/emutos.img").read_bytes()
if len(data) != 256 * 1024:
raise SystemExit(f"unexpected EmuTOS ROM size: {len(data)}")
reset = int.from_bytes(data[4:8], "big") & 0xffffff
# Steem SSE get_TOS_address() does not inspect the reset vector.
# It reads bytes 8..11 and accepts only an exact $E00000/$FC0000
# ROM base encoded there.
steem_header = int.from_bytes(data[8:12], "big") & 0xffffff
print(f"emutos_size={len(data)}")
print(f"emutos_reset_vector=0x{reset:06x}")
print(f"emutos_steem_header=0x{steem_header:06x}")
print("emutos_first16=" + data[:16].hex())
if steem_header not in (0xe00000, 0xfc0000):
raise SystemExit(f"Steem-incompatible EmuTOS header: 0x{steem_header:06x}")
PY
mkdir -p steem-linux/bin/guest-c steem-linux/bin/PRG
cp artifacts/MINIMAL.PRG steem-linux/bin/guest-c/MINIMAL.PRG
cp artifacts/MINIMAL.PRG steem-linux/bin/PRG/MINIMAL.PRG
rm -f steem-linux/bin/guest-c/MINPASS.TXT steem-linux/bin/PRG/MINPASS.TXT
python3 - <<'PY'
from pathlib import Path
p = Path("steem-linux/bin/steem.ini")
s = p.read_text()
s = s.replace("DisableHardDrives=0", "DisableHardDrives=1")
s = s.replace("PRG_support=0", "PRG_support=1")
lines = s.splitlines()
out = []
in_hd = False
for line in lines:
if line == "[HardDrives]":
in_hd = True
out.append(line)
continue
if in_hd and line.startswith("["):
# Emit one canonical GEMDOS drive and discard every stale
# Drive_N entry inherited from the stock Steem profile.
out.append("Drive_0_Letter=C")
out.append(f"Drive_0_Path={Path.cwd() / 'steem-linux/bin/PRG'}")
out.append("BootDrive=0")
in_hd = False
out.append(line)
continue
if in_hd and (line.startswith("Drive_") or line.startswith("BootDrive=")):
continue
out.append(line)
if in_hd:
out.append("Drive_0_Letter=C")
out.append(f"Drive_0_Path={Path.cwd() / 'steem-linux/bin/PRG'}")
out.append("BootDrive=0")
p.write_text("\n".join(out) + "\n")
PY
grep -F 'Drive_0_Letter=C' steem-linux/bin/steem.ini
grep -F 'Drive_1_Letter=NOT ASSIGNED' steem-linux/bin/steem.ini
grep -F 'Drive_2_Letter=NOT ASSIGNED' steem-linux/bin/steem.ini
grep -F 'DisableHardDrives=1' steem-linux/bin/steem.ini
grep -F 'PRG_support=1' steem-linux/bin/steem.ini
grep -F 'Drive_0_Letter=C' steem-linux/bin/steem.ini
grep -F "Drive_0_Path=$GITHUB_WORKSPACE/steem-linux/bin/PRG" steem-linux/bin/steem.ini
grep -F 'BootDrive=0' steem-linux/bin/steem.ini
- name: Probe EmuTOS boot and capture display evidence
run: |
set -eux
Xvfb :99 -screen 0 1024x768x24 >/tmp/steem-xvfb.log 2>&1 &
xvfb_pid=$!
export DISPLAY=:99
sleep 1
(
cd steem-linux/bin
strace -ff -o "$GITHUB_WORKSPACE/steem-sse-strace" ./xsteem64 \
"INI=$GITHUB_WORKSPACE/steem-linux/bin/steem.ini"
) >steem-sse-stdout.log 2>steem-sse-stderr.log &
steem_pid=$!
# Explicit INI loading changes startup timing. Poll rather than
# assuming the main X11 window exists after exactly three seconds.
win=""
for attempt in $(seq 1 15); do
win="$(xdotool search --onlyvisible --name 'Steem' 2>/dev/null | head -n 1 || true)"
[ -n "$win" ] && break
if ! kill -0 "$steem_pid" 2>/dev/null; then
echo "Steem exited before creating its main window" >&2
break
fi
sleep 1
done
if [ -z "$win" ]; then
xdotool search --onlyvisible --name '.*' 2>/dev/null | while read -r wid; do
printf 'WINDOW %s class=%s name=%s\\n' "$wid" \
"$(xdotool getwindowclassname "$wid" 2>/dev/null || true)" \
"$(xdotool getwindowname "$wid" 2>/dev/null || true)"
done | tee steem-sse-visible-windows.txt
fi
test -n "$win"
# Focus Steem's main window and activate its visible Run control.
# The Linux port did not react to an unfocused F12 event in CI.
# Bare Xvfb has no window manager, so _NET_ACTIVE_WINDOW is unavailable.
# A window-relative click does not require WM focus/activation.
xdotool windowraise "$win" || true
sleep 1
xdotool getwindowgeometry "$win" | tee steem-sse-window.txt
# Re-trigger M3.2 after refining the Run target. Artifact evidence confirms the Run/play triangle occupies the
# top-left toolbar at window-relative ~12,33. The previous 10,10 coordinate was above the actual play triangle; 22,82
# landed in the black Atari framebuffer, so it could
# never start emulation.
xdotool mousemove --window "$win" 12 33 click 1
sleep 2
# Fresh Steem profiles show a 400x150 "Welcome to Steem SSE"
# modal. Its Continue button is the 100x25 child at the lower right
# of the dialog. Click relative to the modal so Xvfb placement does
# not matter.
welcome="$(xdotool search --onlyvisible --name 'Welcome to Steem SSE' | head -n 1 || true)"
if [ -n "$welcome" ]; then
# The two 100x25 child windows are the modal buttons. Click the
# right-most child rather than guessing coordinates in the parent.
# Child positions are relative to their parent, so simply use
# the last visible 100x25 child: Steem creates the two modal
# buttons in left-to-right order and Continue is the latter.
button=""
for wid in $(xdotool search --onlyvisible --name '.*'); do
size="$(xdotool getwindowgeometry "$wid" 2>/dev/null | awk '/Geometry:/ {print $2}')"
if [ "$size" = "100x25" ]; then
# The first 100x25 child is Continue. The second is Exit;
# selecting the latter caused Steem to save state and exit(1).
button="$wid"
break
fi
done
test -n "$button"
# xdotool's --window click is unreliable for these custom HXC
# controls. Move into the child window and issue a normal click.
xdotool mousemove --window "$button" 50 12
xdotool mousedown 1
sleep 0.2
xdotool mouseup 1
fi
sleep 5
# A remaining TOS Image dialog means Steem rejected the configured
# active ROM. Capture enough X11 structure to identify the exact
# first-run controls before attempting any further automation.
tos_dialog="$(xdotool search --onlyvisible --name 'TOS Image' | head -n 1 || true)"
if [ -n "$tos_dialog" ]; then
xdotool getwindowgeometry "$tos_dialog" | tee steem-sse-tos-dialog.txt
xwininfo -tree -id "$tos_dialog" | tee steem-sse-tos-dialog-tree.txt
xprop -id "$tos_dialog" | tee steem-sse-tos-dialog-xprop.txt
# The dialog is a custom-drawn X11 window: its only actionable
# child is a 100x25 button at +250+148. Record it explicitly and
# probe that button instead of invoking the native file selector.
tos_button="$(xwininfo -tree -id "$tos_dialog" | awk '/100x25[+]250[+]148/ {print $1; exit}')"
if [ -n "$tos_button" ]; then
xdotool windowraise "$tos_dialog" || true
xdotool mousemove --window "$tos_button" 50 12
xdotool mousedown 1
sleep 0.2
xdotool mouseup 1
sleep 2
import -display :99 -window root steem-sse-tos-button-result.png
xdotool search --onlyvisible --name '.*' getwindowname %@ 2>/dev/null | tee steem-sse-after-tos-button-windows.txt || true
fi
for child in $(xwininfo -tree -id "$tos_dialog" | awk '$1 ~ /^0x[0-9a-f]+$/ {print $1}' | tail -n +2); do
echo "=== $child ==="
xwininfo -id "$child" 2>/dev/null || true
xprop -id "$child" 2>/dev/null || true
done | tee steem-sse-tos-dialog-children.txt
import -display :99 -window root steem-sse-tos-dialog.png
echo "Steem rejected configured EmuTOS ROM" >&2
exit 86
fi
if ! kill -0 "$steem_pid" 2>/dev/null; then
set +e
wait "$steem_pid"
steem_rc=$?
set -e
printf 'steem_exit_status=%s\n' "$steem_rc" | tee steem-sse-exit-status.txt
echo "Steem exited after welcome dialog" >&2
{
echo "=== stderr ==="
tail -200 steem-sse-stderr.log || true
echo "=== strace tail ==="
for trace in steem-sse-strace*; do
[ -f "$trace" ] || continue
echo "--- $trace ---"
tail -120 "$trace"
done
} | tee steem-sse-exit-diagnostics.txt
exit "$steem_rc"
fi
printf 'steem_exit_status=running\n' | tee steem-sse-exit-status.txt
# After Continue Steem may keep the emulator as an untitled X11
# window. Discover all visible top-level windows and select the
# largest one rather than relying on a title.
xdotool search --onlyvisible --name '.*' | while read -r wid; do
name="$(xdotool getwindowname "$wid" 2>/dev/null || true)"
geom="$(xdotool getwindowgeometry "$wid" 2>/dev/null || true)"
printf 'WINDOW %s %s\\n%s\\n' "$wid" "$name" "$geom"
done | tee steem-sse-visible-windows-after-first-run.txt
# Prefer the actual Steem/X11 window class. The X root window
# is also returned by xdotool and must never be selected.
win=""
for wid in $(xdotool search --onlyvisible --name '.*'); do
cls="$(xdotool getwindowclassname "$wid" 2>/dev/null || true)"
name="$(xdotool getwindowname "$wid" 2>/dev/null || true)"
printf 'CANDIDATE %s class=%s name=%s\\n' "$wid" "$cls" "$name"
case "$cls $name" in
*Steem*|*steem*|*xsteem*|*STEEM*)
case "$name" in
"Welcome to Steem SSE") ;;
*) win="$wid"; break ;;
esac
;;
esac
done
# If Steem has an untitled class, use the TOS Image child only as a
# deterministic fallback; never select the X root (window 543).
if [ -z "$win" ]; then
win="$(xdotool search --onlyvisible --name 'TOS Image' | head -n 1 || true)"
fi
if [ -z "$win" ]; then
best_area=0
for wid in $(xdotool search --onlyvisible --name '.*'); do
[ "$wid" = "543" ] && continue
size="$(xdotool getwindowgeometry "$wid" 2>/dev/null | awk '/Geometry:/ {print $2}')"
w="${size%x*}"
h="${size#*x}"
case "$w:$h" in *[!0-9:]*|:) continue ;; esac
area=$((w * h))
if [ "$area" -gt "$best_area" ]; then
best_area="$area"
win="$wid"
fi
done
fi
test -n "$win"
xdotool windowraise "$win" || true
xdotool getwindowgeometry "$win" | tee steem-sse-window.txt
# The play triangle is the left-most toolbar control. Artifact
# screenshots place it at window-relative ~12,33; click that exact
# control instead of the framebuffer or relying on F12 delivery.
# The AUTO disk is now mounted before startup. Perform a cold reset
# after Run so EmuTOS boots from drive A and scans A:\\AUTO.
# Capture the exact toolbar state around the Run interaction. The
# play triangle is at window-relative 10,10 in the current 642x423
# Linux window; retain before/after evidence instead of inferring
# execution from framebuffer colour count alone.
import -display :99 -window "$win" steem-sse-before-run.png
xdotool mousemove --window "$win" 10 10
xdotool mousedown 1
sleep 0.2
xdotool mouseup 1
sleep 2
# The first Run click now has deterministic evidence: it transitions
# Steem from the stopped black display into its white boot/status
# screen. Give EmuTOS substantially longer than the old 15-second
# window to finish cold boot and AUTO processing on the CI runner.
import -display :99 -window "$win" steem-sse-after-run.png
compare -metric AE steem-sse-before-run.png steem-sse-after-run.png null: 2>steem-sse-run-pixel-diff.txt || true
xdotool search --onlyvisible --name '.*' getwindowname %@ 2>/dev/null | tee steem-sse-after-run-windows.txt || true
# Do not inject a guessed reset-toolbar coordinate here. Steem has
# already loaded drive A from steem.ini before the first Run, so the
# initial Run is the cold boot used for AUTO qualification.
for boot_wait in $(seq 1 12); do
if [ -s steem-linux/bin/PRG/MINPASS.TXT ]; then
echo "AUTO marker appeared after $((boot_wait * 5)) seconds"
break
fi
if mdir -i steem-linux/bin/minimal-auto.st ::/MINPASS.TXT >/dev/null 2>&1 || \
mdir -i steem-linux/bin/minimal-auto.st ::/AUTO/MINPASS.TXT >/dev/null 2>&1; then
echo "AUTO floppy marker appeared after $((boot_wait * 5)) seconds"
break
fi
sleep 5
done
xdotool getmouselocation | tee steem-sse-run-click.txt
import -display :99 -window root steem-sse-emutos-boot.png
# M3.2c: exercise the running guest desktop directly. First prove
# that Steem is rendering an Atari framebuffer, not merely its X11
# toolbar/window chrome. The framebuffer area must contain visible
# non-black guest pixels before we attempt GEM interaction.
import -display :99 -window root steem-sse-prg-dialog.png
test -s steem-sse-prg-dialog.png
guest_colors="$(convert steem-sse-prg-dialog.png -crop 640x380+201+242 +repage -format '%k' info:)"
printf 'guest_framebuffer_colors=%s\n' "$guest_colors" | tee steem-sse-guest-framebuffer.txt
if [ "$guest_colors" -le 1 ]; then
echo "Steem window exists but Atari framebuffer is still blank; refusing false guest interaction" >&2
exit 87
fi
# The cropped evidence from run #90 proves the earlier guessed
# desktop coordinate did not open C:. Use GEM's keyboard desktop
# shortcut instead: Alt+Z opens drive Z without depending on icon
# placement or Steem window decorations.
# Xvfb has no window manager. Focus the X11 window directly, then
# use GEM's standard Alt+F "File" menu path rather than relying on
# an unverified Alt+Z drive shortcut. Select "Open" on the desktop
# after selecting the C: icon via keyboard navigation.
xdotool windowfocus --sync "$win"
# Drive C is configured as the sole hard drive. Probe the visible
# GEM desktop systematically instead of assuming keyboard selection:
# double-click a small grid of likely drive-icon positions and stop
# as soon as the framebuffer changes.
# No GEM desktop automation is required for execution qualification.
# MINIMAL.PRG is in the floppy AUTO directory and must execute during
# EmuTOS boot. Keep the post-boot framebuffer as evidence.
sleep 5
import -display :99 -window root steem-sse-after-auto.png
test -s steem-sse-emutos-boot.png
test "$(stat -c %s steem-sse-emutos-boot.png)" -gt 1000
identify steem-sse-emutos-boot.png | tee steem-sse-emutos-boot.txt
# steem_pid is the strace wrapper; terminating only it can leave
# xsteem64 alive and keep the Actions step open. Stop the traced
# emulator first, then reap the wrapper.
pkill -TERM -f '/xsteem64' 2>/dev/null || true
sleep 1
pkill -KILL -f '/xsteem64' 2>/dev/null || true
kill "$steem_pid" 2>/dev/null || true
wait "$steem_pid" 2>/dev/null || true
kill "$xvfb_pid" 2>/dev/null || true
wait "$xvfb_pid" 2>/dev/null || true
printf 'boot_screenshot=PASS\n' | tee steem-sse-result.txt
# Native [Disks] drive-A loading is authoritative for this qualification.
# Keep the disk image in the artifact and inspect Steem's trace for
# an actual open/read of it before accepting the run.
if ! grep -Fq "minimal-auto.st" steem-sse-strace*; then
echo "Steem did not open the canonical AUTO floppy image" >&2
exit 90
fi
marker='Ploos-AS atari-runtime minimal GEMDOS execution PASS'
# AUTO execution happens from the floppy filesystem, so retrieve the
# marker from either the filesystem root or AUTO/ depending on the
# program's current-directory semantics.
# runtime-probe.c writes a relative MINPASS.TXT. Preserve a marker
# produced through the configured GEMDOS C: mapping before falling
# back to reading the writable AUTO floppy.
if [ -s steem-linux/bin/PRG/MINPASS.TXT ]; then
echo "marker_source=gemdos-c" | tee -a steem-sse-result.txt
else
mcopy -i steem-linux/bin/minimal-auto.st ::/MINPASS.TXT steem-linux/bin/PRG/MINPASS.TXT || true
fi
if [ ! -s steem-linux/bin/PRG/MINPASS.TXT ]; then
mcopy -i steem-linux/bin/minimal-auto.st ::/AUTO/MINPASS.TXT steem-linux/bin/PRG/MINPASS.TXT || true
fi
test -s steem-linux/bin/PRG/MINPASS.TXT
# GEMDOS text files use CRLF. Normalize the guest marker before
# exact comparison so the qualification checks content rather than
# host-vs-guest newline convention.
tr -d '\r' < steem-linux/bin/PRG/MINPASS.TXT > steem-linux/bin/PRG/MINPASS.normalized.txt
grep -Fx "$marker" steem-linux/bin/PRG/MINPASS.normalized.txt
printf 'guest_marker=PASS\n' | tee -a steem-sse-result.txt
- name: Collect GDB backtrace after startup crash
if: always()
run: |
if grep -Fxq 'exit_code=139' steem-sse-result.txt 2>/dev/null; then
Xvfb :99 -screen 0 1024x768x24 >/tmp/steem-xvfb.log 2>&1 &
xvfb_pid=$!
export DISPLAY=:99
sleep 1
set +e
(
cd steem-linux/bin
timeout --signal=TERM --kill-after=2s 15s gdb --batch -ex 'set pagination off' -ex run -ex 'thread apply all bt full' --args ./xsteem64
) >steem-sse-gdb.log 2>&1
gdb_rc=$?
set -e
kill "$xvfb_pid" 2>/dev/null || true
wait "$xvfb_pid" 2>/dev/null || true
printf 'gdb_exit_code=%s\n' "$gdb_rc" >> steem-sse-result.txt
cat steem-sse-gdb.log
exit 1
fi
- name: Upload probe evidence
if: always()
uses: actions/upload-artifact@v4
with:
name: steem-sse-linux-probe
path: |
steem-sse-revision.txt
steem-sse-sha256.txt
steem-sse-capsimage-sha256.txt
steem-sse-ldd.txt
x11-fonts.txt
x11-font-candidates.txt
steem-sse-emutos-version.txt
steem-sse-emutos-sha256.txt
steem-sse-provisioned-rom-sha256.txt
steem-sse-emutos-boot.png
steem-sse-emutos-boot.txt
steem-sse-prg-dialog.png
steem-sse-before-drive.png
steem-sse-before-drive-root.png
steem-sse-drive-click.txt
steem-sse-drive-open.png
steem-sse-window.txt
steem-sse-run-click.txt
steem-sse-before-run.png
steem-sse-after-run.png
steem-sse-run-pixel-diff.txt
steem-sse-after-run-windows.txt
steem-sse-run-button.txt
steem-sse-guest-framebuffer.txt
steem-sse-visible-windows.txt
steem-sse-stdout.log
steem-sse-stderr.log
steem-sse-exit-status.txt
steem-sse-exit-diagnostics.txt
steem-sse-strace*
steem-sse-gdb.log
steem-sse-result.txt
steem-linux/bin/PRG/MINPASS.TXT
if-no-files-found: error