86Box VM operations
Use this skill whenever a task requires starting, configuring, waiting for, restarting, debugging, or inspecting an 86Box virtual machine.
Non-negotiable safety rules
- Resolve the exact VM directory before launch.
- Launch with
--vmpath <VM directory> --vmname <display name>. - Track the PID returned for that launch and the stable harness process name.
- Verify PID identity using the executable and exact
--vmpathbefore any signal. - Never use
killall, broadpkill, broadpgrep, or an unverified PID. - Assume other people are running independent 86Box processes.
- Prefer clean UI shutdown when NVRAM or configuration changes matter.
- Never overwrite shared ROMs for a one-VM experiment; clone a complete ROM set and select it with
--rompath.
Preflight
-
Resolve a name under the platform VM root. On macOS:
~/Library/Application Support/86Box/Virtual Machines/<NAME>A direct VM path is also valid.
-
Read
<VM>/86box.cfg. Record:machine;cpu_use_dynarec;- optional
gdbstub_port; - configured media and adapter devices.
-
Choose the binary:
- normal interaction:
/Applications/86Box.app/Contents/MacOS/86Box; - guest debugging: the local
-DGDBSTUB=ONbuild.
- normal interaction:
-
Audit the command (run from the 86Box-tool project root, or install it):
cd ~/src/86Box-tool && uv run ./86boxctl.sh <VM> command -
If debugging multiple VMs, require a distinct
gdbstub_portin every config andcpu_use_dynarec = 0.
Launch
Standalone controller:
./86boxctl.sh <VM> start
./86boxctl.sh <VM> start --gdb-stub
Patched IBM PS/2 Model 80 Type 2 BIOS:
./86boxctl.sh <VM> command \
--model-80-type-2-rom /path/to/sequential-128k.bin
./86boxctl.sh <VM> start \
--model-80-type-2-rom /path/to/sequential-128k.bin
The VM must use machine = ibmps2_m80. The controller discovers the platform
stock ROM root, clones the complete tree under <VM>/romsets/, splits the
image into the active even/odd lane files, validates them, and launches with
that generated root through --rompath. Use --rom-source when discovery is
ambiguous. Use prepare-roms to build without launching.
For a long harness-mediated GUI session, start the executable through the harness process manager with:
name = unique stable name such as 86box-automatic-configuration
application = selected 86Box binary
args = [--vmpath, <absolute VM dir>, --vmname, <display name>]
pty = false
persist = true when interaction spans turns
Record the returned PID. Use only that stable process name for subsequent
describe, logs, restart, and stop operations. A restart produces a new
PID; record it.
Do not pass 86box.cfg as a lone positional argument. That selects a config
but does not set 86Box's VM user-files path, so relative media, NVRAM, and local
ROMs can resolve against the wrong directory.
Establish readiness
Process creation alone is not guest readiness.
- Normal build: require the exact process to survive startup, then inspect the visible VM or a concrete VM artifact.
- GDB build: require that this VM's configured RSP port listens.
- Never interpret another 86Box process or port as evidence for this VM.
Use:
./86boxctl.sh <VM> status
./86boxctl.sh <VM> logs --lines 100
Wait for a user
When the user says to wait until a harness interruption, do exactly that. Do not advance when the VM exits unless the user requested exit-triggered work. The harness job waiter does not treat a managed GUI process as a background job, so create a long finite asynchronous wait sentinel, wait on that job with an indefinite harness timeout, and cancel it after the user's interruption.
Keep the VM process separately tracked by its stable process name and PID.
Observe
Host lifecycle
./86boxctl.sh <VM> status
./86boxctl.sh <VM> wait
./86boxctl.sh <VM> logs --lines 100
Guest screen, console, and memory
Two independent paths:
-
RSP stub (requires the GDB-stub build; connecting pauses the guest):
RSP_PORT=<port> ./rsp.py probe RSP_PORT=<port> ./rsp.py video-mode RSP_PORT=<port> ./rsp.py screen RSP_PORT=<port> ./rsp.py wait-screen "expected text" --wait-timeout 600 RSP_PORT=<port> ./rsp.py dump b8000 fa0In text modes
0,1,2,3, and7,screenselects 40x25 or 80x25, usesB8000hfor colour orB0000hfor mono, and follows the active video page. In graphics mode11h, it reads the packed 640x480 1bpp framebuffer atA0000h, resumes the guest, and asks the harness's configured@visionmodel to interpret the generated PNG.ompmust be onPATH; use--vision-questionto customize the prompt. Snapshot commands pause only for inspection and detach before model inference. -
Memdump server (no GDB stub, no CPU pause): set
memdump_portin the VM config. Poll the text framebuffer with a live delta view:./screenmon.py --memdump-port 12348 # interactive TTY: ANSI delta ./screenmon.py --memdump-port 12348 --plain # piped: full frame + changed rows ./screenmon.py --memdump-port 12348 --count 30 --interval 0.1screenmon.pyrenders only what changed between polls; the mode is chosen byisatty()unless--full/--plainoverride it. The default interval is 0.1 s.
For continuous bidirectional operation:
# Interactive ANSI mirror; Ctrl-] detaches
RSP_PORT=<port> ./guest_console.py
# Processable screen and delta events
RSP_PORT=<port> ./guest_console.py \
--format jsonl --no-input --duration 10
# Type a DOS command through the BIOS ring
RSP_PORT=<port> ./guest_console.py \
--send "DIR" --enter --duration 10
# Early POST requires hardware scan-code injection
RSP_PORT=<port> ./guest_console.py \
--input-mode hardware --key F1 --duration 5
The console samples at 70 Hz, retains the complete character/attribute buffer,
and emits only coalesced changed runs. A TTY gets ANSI cursor updates;
redirected output defaults to JSONL. Input defaults to the validated BIOS
keyboard ring after boot. Hardware mode injects paced set-1 scan bytes through
the emulated 8042 for POST. Keep Ctrl-] for detach so guest Ctrl-C remains
available.
Never infer success merely from an injected key. Observe the resulting screen delta or explicit guest output.
Persistent VM configuration
After a clean shutdown, inspect only the resolved VM directory:
<VM>/86box.cfg
<VM>/nvr/<machine>.nvr
<VM>/nvr/<machine>_sec.nvr
For this repository:
uv run --project . refdisk-view-config --vm <NAME> --adf-source rf7080a/disk
Adding a card in the 86Box UI does not by itself populate guest MCA NVRAM. The guest Reference Disk must configure POS state and 86Box must save it cleanly.
ROM verification
machine = ... determines required ROM filenames. Check:
- the exact
--rompathrecorded by86boxctl status, when present; - the generated set's
.86boxctl-romset.json; - the exact active filenames from the 86Box machine definition;
- hashes or byte comparisons of those active files.
For this workflow, an explicit --rompath must contain the complete 86Box ROM
set. Although 86Box can fall back to other search roots, completeness makes the
run self-contained and prevents silent mixing. Do not point it at a directory
containing only one machine.
For a raw sequential 128 KiB Model 80 Type 2 image, use
--model-80-type-2-rom. The controller writes:
machines/ibmps2_m80/15f6637.bin = input[0::2] # low/even lane
machines/ibmps2_m80/15f6639.bin = input[1::2] # high/odd lane
Both files are 65,536 bytes; interleaving them must reproduce the input.
start and restart create or validate the isolated clone automatically:
./86boxctl.sh <VM> prepare-roms \
--model-80-type-2-rom /path/to/sequential-128k.bin
./86boxctl.sh <VM> status
Defaults are the standard 86Box ROM roots: macOS Application Support,
XDG_DATA_HOME/XDG_DATA_DIRS on Unix, and Local/Program/AppData on Windows.
Override with --rom-source PATH or 86BOX_ROM_ROOT. --refresh-rom-set
rebuilds only a recognized generated directory.
Backup suffixes such as .orig and .altered are not selected automatically.
Preserve shared originals.
For an end-to-end proof using a real GDB-stub-enabled 86Box process:
./model80_rom_smoke.py <MODEL80_VM>
Require machine = ibmps2_m80, cpu_use_dynarec = 0, and a unique
gdbstub_port. The harness derives a sequential image from the stock lanes,
changes KB OK to OKBMR, repairs the 64 KiB checksum, launches through the
isolated complete ROM clone, observes OKBMR in guest text VRAM, and stops only
its recorded PID. Use --keep-running only when manual inspection is required.
Stop or recover
Prefer a clean UI shutdown, then confirm the tracked process exited. If control is required:
./86boxctl.sh <VM> stop
./86boxctl.sh <VM> restart
stop sends SIGTERM only to the identity-verified PID and does not silently
force-kill it. Use --kill-after-timeout only after accepting possible NVRAM
or configuration loss.
On a crash:
- capture
statusandlogs; - confirm the record belongs to the intended VM;
- restart that record only;
- record the new PID;
- return to the prior wait or observation step.
Completion evidence
Before reporting success, provide the evidence appropriate to the task:
- exact VM path and machine identifier;
- binary and exact launch command;
- stable process name and current PID;
- readiness evidence;
- clean exit or crash status;
- relevant config/NVRAM paths and sizes;
- guest screen, register, memory, or decoded configuration output.
See README.md for implementation details, GDB-stub quirks, ROM lookup, and
failure diagnosis.