The ADP debugger

The Advanced Debugging Protocol (ADP) is a plain-text, line-oriented TCP protocol on port 11001. Send a command line, read the text reply. Any socket client can drive the machine with no recompile — ideal for scripting and for quick, editor-independent inspection. It’s the primary, verified debugging surface in Bizmuth (Rhai scripting is built on top).

The core is single-threaded: the CPU, video, memory and debugger all run on one thread, and commands are applied between instructions — never mid-instruction — so state you read is always consistent. Numeric arguments accept decimal or 0x/$ hex.

Connecting

The ADP server is on by default (change the port with --adp-port, or pick protocols with --debug-protocol adp|dzrp|both|none). Connect however you like:

nc localhost 11001            # interactive: type a command, read the reply

help (or ?) prints the live command list; an unknown verb replies unknown command '…' (try 'help').

Message framing. Every message block — the banner, a command reply, or an async event — ends with a blank line, so a client can frame a multi-line reply (mem, disasm, reg, bp list) by reading lines until an empty one. A first line beginning with * is an unsolicited async event, not a reply. For automated/CI use, --headless runs the server with no window until the process is killed.

Execution control

CommandReply / effect
c / continueresume free-running execution → running
pausestop at the current instruction (snapshots for step-back) → paused pc=XXXX
s / step [n]single-step n instructions (default 1) → pc=XXXXstep-into
next / stepoverstep over a CALL/RST/block-op (run to the following instruction); a plain single-step otherwise
sb / stepback [n]reverse-step n snapshots → stepped back; pc=XXXX (N states left)
runto / run <addr>run to <addr> via a one-shot breakpoint (auto-removed when it fires)
finish / finstep out — run to the current subroutine’s return address
frameprint the frame counter → frame N
pctrace <file>from now on, append (pc, sp) per instruction to <file> (gate a deep trace to a region)

Step-back replays saved full-machine snapshots — exact, not heuristic, but only reaches as far back as the snapshot history (snapshots are taken before each step/pause, not during free-run). no earlier state in history means you free-ran past the recorded window — re-run and step in.

Async stop events. While the machine is free-running (after continue/runto/finish/next), a stop is pushed to the client as an unsolicited line prefixed with * — so you learn of a breakpoint hit without polling: *stopped pc=XXXX reason=bp id=N. The leading * marks it apart from command replies, so a DAP / IDE bridge maps it straight to a DAP stopped event.

Inspection

CommandReply / effect
stateone-line CPU + paging summary: pc=.. sp=.. af=.. bc=.. de=.. hl=.. ix=.. iy=.. i=.. im=.. iff=..
reg [name[=val]]dump all registers / read one / set one. Names: af bc de hl ix iy sp pc af' bc' de' hl' i r
mem <addr> [len]hex-dump len bytes (default 16, max 0x1000) — paging-resolved (what the CPU sees)
mem write <addr> <hex…>poke bytes (paging-resolved), e.g. mem write 8000 DE AD BE EF
d / disasm <addr> [n]disassemble n instructions (default 8), including Z80N opcodes
bt / backtraceshadow call/interrupt stack (needs debugging enabled early)
isrinside an interrupt handler? → in_isr=.. (pc=..)
overlays / ovactive memory overlays (bootrom / DivMMC automap / config-mode / alt-ROM / MMU slots) — which ROM mem/disasm show at $0000–$3FFF
nr <reg>read a nextreg (hex) → nr XX = XX (raw XX) — the CPU read-back and the stored raw byte (they can differ)
get <path>read any serialized state field by dotted path (see below)

mem / disasm resolve through the live MMU and overlays (bootrom, Layer 2 paging, config mode, DivMMC, Multiface), so they show exactly what the CPU would fetch — not raw bank RAM.

Video, sprites & DMA

See Inspecting hardware for these in depth.

CommandReply / effect
sprites / sprvisible sprites: raw attributes + resolved anchor/relative position, transform and pattern
sprslots <start> [end]sprite pattern slots start..=end as JSON (256 bytes/slot)
sprram <path.png>render sprite pattern RAM to a colour PNG (live palette)
layer / ly [name] [on|off]list layer visibility, or isolate a layer (ula|lores|tilemap|layer2|sprites; toggles if on/off omitted)
screenshot / shot <path.png> [layers]save the frame; layers = comma list of ula,lores,tilemap,layer2,sprites to capture only those
bankshot <page> <path.png>render a physical RAM page as the ULA/Timex screen (view an off-screen buffer bank)
dmarecent DMA transfers (src[mode] dest[mode] count + I/O flags)
copper / copcopper state — mode / exec address + the decoded WAIT/MOVE/NOP list

Breakpoints & watchpoints

CommandEffect
bp add pc <addr> [if <cond>]add a PC breakpoint, optionally conditional
bp add pc <pg>:<off>page-qualified (physical) breakpoint for banked code — fires wherever the CPU executes at physical 8 KB page <pg>, offset <off> (both hex, off in 0000..1FFF), e.g. bp add pc 05:1118. Banking-robust; maps to a debug-info (page, off) location for source-level debugging.
bp add mem <addr> [r|w|rw]add a memory watchpoint — break on read / write / either (default rw)
bp listlist breakpoints with their labels
bp del <id>remove one
bp clearremove all

The optional if <cond> is only evaluated once the address already matched, so the hot path stays a cheap compare — see Conditional breakpoints for the full condition language (registers, flags, memory derefs, nr[]/nrraw[] nextregs).

State & snapshots

CommandReply / effect
savestate <file>write a full compressed savestate (AZST) → saved <file>
loadstate <file>restore a full savestate → loaded <file>
loadsnapshot / loadsnap <file>load a .sna/.snx/.z80/.nex via the host loader, then pause → loaded <file>; paused pc=XXXX

State round-trips faithfully; behaviour after a load can still diverge for programs that read the host-backed RTC (live wall-clock). The frame counter resumes at the saved frame, so on_frame handlers keyed off absolute frame numbers should use load-relative offsets.

Paths with spaces must be double-quoted — savestate "/tmp/my save.azst" (inside quotes, \ escapes the next char). The tokenizer is quote-aware, so every path-bearing command (savestate, loadstate, loadsnapshot, screenshot, script load) handles spaces.

Input injection

Drive the machine with no window — navigate the NextZXOS browser and launch programs headlessly.

CommandReply / effect
key <tok> [frames]inject a keypress held frames frames (default 8), then release → pressed <tok> for N frames
enter / down / up / left / right / space / y / nshorthand for key <tok>

Tokens map to the ZX key matrix; cursor keys use the NextZXOS browser mapping (down = key 6, up = key 7). Keys only advance while the engine is running (continue), not while paused — e.g. down×N then enter to launch a browser entry.

Scripting & session

CommandReply / effect
script load <file>load a Rhai script → loaded <file>; handlers: [on_frame, …]
script eval <expr>evaluate a Rhai expression against live state
script listlist active handlers
help / ?command list
q / quitclose this connection (the machine keeps running)

See Rhai scripting for the scripting layer.

get — state interrogation

get <path> walks the machine’s serialized state tree — the same fields the savestate captures — and prints the addressed value as JSON. Paths are dotted, with [index] for arrays:

get bus.memory.mmu.slots        → [255,255,10,11,4,5,0,1]   (the 8 MMU slot banks)
get bus.next_regs.regs[192]     → 8                          ($C0 raw, decimal index 192)
get bus.ula.frame               → 1386                       (the ULA frame counter)
get cpu                         → {…}                         (the whole CPU sub-object)

Useful roots: cpu, bus, bus.memory.mmu.slots, bus.next_regs.regs (the raw 256-entry nextreg array — the stored/written byte), bus.layer2.*, bus.lores.*, bus.sprites.*. A missing field replies no such field: <name> and an out-of-range index index N out of range, so you can probe the tree incrementally. Large RAM blobs are elided.

Example session

> state
pc=0C8F sp=5BF3 af=0344 bc=02DF de=7D30 hl=5C3B ix=DBD0 iy=5C3A i=09 im=1 iff=11
> nr 22
nr 22 = 00 (raw 00)
> bp add pc 0x8000 if a == 0x1e
bp #0 added
> mem 0x4000 8
4000  00 00 FE 01 FE 01 FE 00
> screenshot /tmp/frame.png layer2,sprites
saved screenshot (640x256) to /tmp/frame.png
> c
running

For automation, layer conditions and reacting to hardware events, build on ADP with Rhai scripting.