Repository navigation
Record M4.3 Falcon 040 qualification #125
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |