Imported from slaapliedje/OpenUA (
.claude/skills/run-amiga-port/SKILL.md). Install upstream withnpx skills add slaapliedje/OpenUA --skill run-amiga-port. Copyright stays with the author.
This repo cross-compiles frua (an AmigaOS hunk executable) for Amiga AGA
(A1200) and runs it in the amiberry emulator (flatpak). The handle is
.claude/skills/run-amiga-port/driver.sh — it builds, boots, waits for
the engine's own boot log, screenshots, and injects input with the timing
quirks amiberry needs. Paths below are relative to the repo root.
Prerequisites (all verified present here)
- Bebbo m68k-amigaos-gcc at
~/opt/amiga— NOT a stock package; build perdocs/toolchain-amiga.md. Verify:~/opt/amiga/bin/m68k-amigaos-gcc --version. - amiberry flatpak:
flatpak run com.blitterstudio.amiberryresolves. - Kickstart 3.2 at
~/Amiberry/ROMs/kicka1200.romand the machine config~/Amiberry/Configurations/openua.uae(A1200, AGA, 2MB chip + 8MB fast,filesystem2mountingdata/work/amiga-mountas DH0,sound_output=exact). - Game data staged in
data/work/amiga-mount/(copyrighted, git-ignored): the FRUA gamedata +S/User-Startupending infrua(run fromS/Startup- sequencebeforeLoadWB). Nostackcommand — frua gets its stack from a__stackglobal (small enough to survive the ~4 KB default; uainst needs more and self-manages via StackSwap). - Host tools:
xdotool,xwininfo, ImageMagickimport,parec(pulse). - A display for the window.
FRUA_AMIGA_DISPLAY(default:0) selects it, and amiberry runs happily on a bare Xvfb — prefer:99so the pointer warping and key injection never touch the user's real desktop. The driver exportsDISPLAYfor the flatpak launch itself, which is load-bearing: flatpak's x11 socket sharing REWRITESDISPLAYinside the sandbox, soflatpak run --env=DISPLAY=:99is silently ignored and the window still opens on the host's:0. Only the launching shell'sDISPLAYdecides.
Run / drive (agent path)
D=.claude/skills/run-amiga-port/driver.sh
"$D" smoke /tmp/amiga-menu.png # build -> boot -> menu screenshot -> stop
Or step by step (state persists in /tmp/frua-amiga between invocations):
"$D" build # make MACHINE=amiga + stage into the mount
"$D" start # boot frua; returns at "menu: modal up" (~40-60s);
# captures the mouse, pointer tracked at (160,100)
"$D" boot [secs] # RAW boot (no frua wait) for uainst / Workbench;
# waits `secs` (default 48) then finds the window
"$D" grab # capture the emulated mouse (centre click -> 160,100)
"$D" shot /tmp/a.png # screenshot the amiberry window
"$D" click 75 133 # move the EMULATED pointer to lores (x,y) + click
"$D" move -10 5 # relative pointer move (updates the tracked pos)
"$D" dclick # best-effort double-click at the pointer (unreliable)
"$D" key Down # one keysym per key; repeats fine: key Down Down
"$D" wait 'regex' [n] # block until DBG.LOG has >= n matches
"$D" log # dump the engine's DBG.LOG boot/debug trail
"$D" sound /tmp/a.wav # SNDTEST build + boot + 55s host-audio capture;
# exits 1 if (near-)silent; restores normal build
"$D" stop # kill amiberry
start waits for frua's own menu: modal up marker, so it ONLY works when
the mount boots frua. To run anything else — the uainst installer, or a
plain Workbench — use boot (which just waits a fixed time) and grab the
mouse yourself.
A correct boot shows the UNLIMITED ADVENTURES main menu ("CURRENT GAME DESIGN: HEIRS.DSN", 2-column button grid) with the blue shield pointer at centre. Menu lores coords: left button column x≈75 — PLAY THE GAME y≈120, SELECT A DESIGN y≈133, CREATE NEW DESIGN y≈146; right column x≈230 — QUIT FROM GAME y≈185.
Running something other than frua (uainst / Workbench)
The mount's S/User-Startup launches frua. To boot the uainst installer
or a plain Workbench instead, swap the startup, boot (raw), then restore.
Always back up and restore — the mount is a real WB3.2 system dir.
cd data/work/amiga-mount
cp S/User-Startup S/User-Startup.bak
# uainst with the ASL requesters (no args) — needs Workbench for a pubscreen,
# so run it AFTER LoadWB, not from User-Startup (which runs before LoadWB):
python3 - <<'PY'
p='S/Startup-sequence'; s=open(p).read()
open(p,'w').write(s.replace('LoadWB\nEndCLI >NIL:',
'LoadWB\nWait 3\nCD DH0:\nuainst\nEndCLI >NIL:'))
PY
printf 'DEVS:Monitors/uaegfx >SYS:MON.LOG\n' > S/User-Startup # drop the frua line
cp ../../../uainst_amiga ./uainst # stage the binary
cp ../../../data/work/fanmods/curse.zip ./curse.zip # a test module
# ...boot, drive, screenshot... then restore:
mv -f S/User-Startup.bak S/User-Startup
git checkout -- S/Startup-sequence 2>/dev/null || true # if tracked
Prefer the CLI-arg path for verifying the extract/convert core — it is
deterministic and needs no mouse: set the startup to uainst DH0:curse.zip DH0:Dest (dest omitted = install into the CWD). Use the GUI (no-arg) path
only to exercise the ASL requesters themselves. ASL-requester driving:
- Click a list row (or the File field) to activate the requester window,
then type — otherwise keystrokes go to the boot console behind it. Typing an
absolute path (
DH0:curse.zip) into the File gadget and pressing Return resolves it. - Return confirms a requester when a string gadget is active (the ZIP requester's File field). It does not confirm a DrawersOnly requester (the "install into which drawer?" one) — you must click its Install here button.
- uainst self-manages its stack (StackSwap, 256 KB), so it runs fine from a
Shell or a double-clicked icon despite the ~4 KB default stack. It also ships
a Workbench icon (
uainst.info, fixed position 12,8) —make installer-amigabuilds both.
Workbench navigation (opening a disk / drawer): single-click an icon's label
to select it (the label is the reliable hitbox — the small glyph is easy to
miss), confirm the highlight in a screenshot, then dclick.
Testing the ECS build (native 32-colour, 68000)
The default config is AGA/68020. To boot the ECS release binary
(release-amiga-ecs — native ECS bitplanes on a plain 68000) you need a
different binary AND an ECS machine config:
make MACHINE=amiga CPU68K=68000 EXTRA_CFLAGS='-DFRUA_FORCE_ECS' # the ECS binary
cp frua data/work/amiga-mount/frua # stage it
AMIBERRY_CONF=~/Amiberry/Configurations/openua-ecs.uae \
.claude/skills/run-amiga-port/driver.sh boot 120 # boot (68000 is SLOW)
openua-ecs.uae is NOT in the repo (amiberry configs live under ~/Amiberry/);
recreate it as an A600-class ECS/68000 machine on a KS3.2 ROM (matches the
WB3.2 mount) with the same filesystem2 DH0 line as openua.uae:
kickstart_rom_file=/home/jfergus/Amiberry/ROMs/CDTVA500A600A2000.47.115.rom
cpu_type=68000
cpu_model=68000
chipset=ecs
chipset_compatible=A600
chipmem_size=4 # 2 MB chip (ECS max)
fastmem_size=4 # 4 MB fast
cpu_speed=real
filesystem2=rw,DH0:OpenUA:/home/jfergus/dev/OpenUA/data/work/amiga-mount,1
gfx_linemode=none
gfx_resolution=lores
gfx_lores=true
gfx_width=360
gfx_height=284
gfx_width_windowed=720
gfx_height_windowed=568
-
The
gfx_*block is LOAD-BEARING, not cosmetic. Without it amiberry falls back to its own display defaults, and on a real desktop (2026-08-20) that rendered the ECS build as a single bar with a grey smudge — no title screen at all — which reads exactly like a broken port. The BUILD was fine; the config was incomplete. 360x284 lores in a 720x568 window (2x, matching the AGA window). For exact-pixel captures (colour counting) useopenua-ecs-exact.uae(1:1 window); for a comfortable human view,openua-ecs-view.uae(3x). -
If you ever see amiberry's own "First-time setup" GUI instead of a boot, the launch is missing
-G. The driver passes it; a hand-rolledflatpak run … --config foo.uaewithout-Gopens the setup screen and never emulates — two debugging sessions started from exactly that. -
Verified boots to the main menu (2026-07-18).
DBG.LOGshows the native path:ecs: 320x200x5 32-colour, per-band copper palette up. -
Budget ~105 s to
menu: modal up(vs ~40 s for AGA/020) — the 7 MHz 68000 spends most of it in frua.rsc + data-pool replay + STRS load. Useboot 120(not the frua-onlystart), then poll DBG.LOG formenu: modal up. -
Restore an AGA build (
make MACHINE=amiga && cp frua data/work/amiga-mount/) when done, so the defaultopenua.uaeconfig works next time. -
startDOES work here despite the older advice to useboot 120— its wait is 60 × 3 s = 180 s, comfortably past the ~105 s ECS boot.
Driving play: the save/load round-trip
Verified end-to-end on AGA and ECS (2026-08-02), the same keystrokes that drive Falcon/TT/ST. No coordinates — see the keyboard-only gotcha below.
export FRUA_AMIGA_DISPLAY=:99 # keep it off the user's desktop
D=.claude/skills/run-amiga-port/driver.sh
"$D" start # AGA; add AMIBERRY_CONF=…/openua-ecs.uae for ECS
"$D" key p # PLAY THE GAME -> the Hall
"$D" key l # LOAD SAVED GAME -> "LOAD WHICH GAME: B"
"$D" key b # the slot letter -> party restored
"$D" key b # BEGIN ADVENTURING -> the dungeon
- Allow 15–25 s between keys on AGA, 25–45 s on ECS; screens paint slowly and a key sent mid-transition is dropped.
- The LOAD picker lists only slots that EXIST (one button for a lone slot B);
SAVE always offers A…J. A "GAME NOT SAVED. LOAD ANYWAY?" confirm appears only
when a party is already loaded — answer
y. - The write path is exercised from camp:
click 252 188(SAVE on the camp bar) then the slot letter;data/work/amiga-mount/<DSN>/SavGam<X>.csvappears on the host within ~35 s. Verified on ECS — 10284 bytes, 46 bytes different from the fixture (position, facing, clock, per-character state). - Fixture:
tools/mk_kobold_design.py+tools/mk_party.py; point the mount at it by writingKOBOLD.DSNinto the first bytes ofdata/work/amiga-mount/start.dat(zero the rest), and restoreHEIRS.DSNafter.
Resuming a save without the keystrokes (autoload.dat)
A one-byte autoload.dat beside the game data — the slot letter A..J —
makes the engine skip BOTH menus on the first pass and land straight in the
saved game, at the saved cell, party restored. It resolves against the current
design from start.dat, so <design>.DSN\SavGam<X>.csv. This replaces the
four-key p / l / slot / b drive in any harness run that just needs to be
in the dungeon.
printf 'B' > data/work/gamedata/autoload.dat # arm it
rm data/work/gamedata/autoload.dat # back to the faithful boot
★ menu: modal up NEVER FIRES when it is armed — both menus are skipped,
and that marker is what start blocks on, so start will sit there until it
times out. Wait for autoload: resumed <path> in the log instead, or boot
the emulator directly. A slot that does not exist logs autoload: no such slot and falls back to the normal menu boot, so an armed-but-wrong
configuration looks like an ordinary boot, not a hang.
Test
make test (host pytest) covers the Amiga c2p transpose
(tests/test_c2p_amiga.py). make MACHINE=amiga CPU68K=68000 must also link
(the ECS/68000 policy check).
Gotchas (all hit live — do not rediscover them)
xdotool windowactivateNEEDS A WINDOW MANAGER, and a bare Xvfb has none. It drives EWMH_NET_ACTIVE_WINDOW; on:99it fails with "your windowmanager claims not to support _NET_ACTIVE_WINDOW". That alone would be harmless — but the driver used to CHAIN it into one invocation (windowactivate --sync $w key p), and the failure aborted the whole chain, so the keystroke was never sent and nothing said so. It cost a session, diagnosed as "Amiga input doesn't commit" and nearly written up as an engine bug. The driver now useswindowfocus, which sets the X input focus directly and needs no WM. If you ever hand-roll anxdotoolcall here, do the same.- Input on the Amiga is FINE — keys and clicks both commit. Verified live
(2026-08-02) on AGA and ECS: letter accelerators drive the menus, and a
normal
click(0.3 s hold) opens ENCAMP and its submenus. If input looks dead, suspect the focus chain above, not the engine. - A slow screen is not a dropped click. On the 7 MHz ECS/68000 build a committed click can take ~30 s to paint the next screen (the camp art load). A "the click didn't register, so I held the button for 2 s and THEN it worked" reading is exactly what that latency fakes — the first click had already landed. Re-sample over ≥45 s before concluding a gesture was lost.
- The engine's own drive is KEYBOARD-ONLY and therefore portable. The main
menu, the Hall and the save/load slot pickers all take letter accelerators
(
p→l→ slot letter →b), so the identical script runs on Falcon, TT, ST, AGA and ECS with no coordinates at all. Reach for the mouse only for controls that have no accelerator. - The emulated mouse is delta-only. amiberry translates host motion into
JOY0DAT deltas after a capture click (
start/grabdo it).click x yworks from a TRACKED position — accurate right after capture; if the game warps its own pointer or you alt-tab, the tracking is stale. Re-anchor by restarting, or navigate bymove+ screenshots. - The delta mapping is NON-LINEAR under Workbench/Intuition. Inside the
frua engine the 1:1 lores tracking holds, but on the WB screen amiberry's
pointer acceleration makes big moves overshoot wildly (observed 0.2–1.1
screen-px per host-px on the same session, direction-dependent). Do NOT trust
a single large
moveto land on a gadget. Technique that works: slam to a corner first (move -900 -900), then step toward the target in SMALL moves (≤ ~60 host px), screenshotting between steps. Budget many iterations for a precise WB target (a button, a scroll arrow). - Double-clicks are the hardest gesture and often just don't fire. amiberry
needs a button-hold long enough to be sampled (below), but WB's
double-click wants two quick clicks — the two requirements fight. Most
reliable: single-click the icon's LABEL to select it, verify the highlight in
a screenshot, then
dclick. Even then, retry. If you only need to prove an icon/tool is valid, a Shell/CLI launch is far cheaper than a WB double-click. - Instant synthetic clicks are invisible. The button is sampled from CIA
PRA at 50Hz;
xdotool clickpresses for microseconds.click/dclickhold ~0.1–0.3s. Never bypass them with a barexdotool click. - One key per xdotool invocation, window focused first. Two keysyms in
one
xdotool keycall lose one. The driver paces them 0.4s apart. xdotool search --name amiberryfinds nothing — the driver usesxwininfo -root -treeinstead.- A silent sound capture may be the EMULATOR config:
sound_output=nonein openua.uae disables Paula output entirely. The driver'ssoundrefuses to run in that case. - Never
pkill -f amiberry(-fcan match your own shell); the driver usespkill -x amiberry. - The picker's design list takes several seconds to build (it loads each design's GAME header); keys typed during the build are deliberately drained by the engine. Wait for the list to render before sending arrows.
- Machine-switch staleness is handled: the Makefile's
.machinestamp purges objects when MACHINE changes — no manualmake clean.
Troubleshooting
| Symptom | Fix |
|---|---|
start times out, no "menu: modal up" |
Stale amiberry holding the mount: pkill -x amiberry, retry. Check tail /tmp/frua-amiga/amiberry.log and data/work/amiga-mount/DBG.LOG for where boot stopped. |
| "Insufficient FAR Memory!" in DBG.LOG | The Bebbo shift-miscompiler workaround was dropped — see toolchain/m68k-amigaos.mk (-fbbb) and docs/toolchain-amiga.md. |
| no amiberry window on the expected display | The flatpak takes DISPLAY from the LAUNCHING SHELL, not from --env (x11 socket sharing overrides it). The driver exports it; if you launch by hand, do DISPLAY=:99 flatpak run …. |
| keys/clicks do nothing at all | The focus chain. windowactivate needs a WM and aborts the whole xdotool invocation on a bare Xvfb — use windowfocus (the driver does). This also breaks start's mouse-capture click, so the emulated pointer is never grabbed. |
| clicks land on the wrong control | Tracked position drifted. stop + start re-anchors at (160,100). |
sound exits 1 with "0s loud" |
Config sound_output (driver checks none, but also verify exact), or pulse default sink changed — the driver records the CURRENT default sink's monitor. |