Skip to content

Copperline Control Protocol: live JSON-RPC control server (--control / --control-gui) and copperline-ctl#201

Merged
LinuxJedi merged 2 commits into
mainfrom
feat/control-protocol
Jul 16, 2026
Merged

Copperline Control Protocol: live JSON-RPC control server (--control / --control-gui) and copperline-ctl#201
LinuxJedi merged 2 commits into
mainfrom
feat/control-protocol

Conversation

@LinuxJedi

Copy link
Copy Markdown
Owner

What this is

A versioned JSON-RPC 2.0 interface over loopback TCP for driving the emulator from scripts, CI, editors, and AI agents -- inspect state, set breakpoints, resume, rewind, inject input, swap media, and capture the display, all within one live session. It completes the original goal of an emulator drivable from the command line: the COPPERLINE_DBG_* headless debugger is snapshot-once at startup, and the GDB remote protocol cannot carry chipset state, media, or input.

Full protocol documentation: docs/debugger/control.md.

Design

Two server modes, one typed command surface. Requests parse into CoreOp (executed identically by both modes through exec_core) or HostOp (resume verbs, input, media -- applied at each driver's own boundary). Everything is a thin dispatch over the existing ui_*/debug_*/tt_* machinery the debugger window, console, and GDB stub already share -- no second debugger implementation.

  • --control ADDR (headless): the server owns the Emulator, the gdbstub::run/serve ownership pattern -- one client at a time, machine paused between sessions, socket polled once per frame/chunk while running (every deterministic stop is still detected per instruction by the core).
  • --control-gui ADDR (windowed): socket threads enqueue parsed, authenticated requests over mpsc; the frame loop drains them at the top of about_to_wait (the same boundary as scheduled-input scripting); an EventLoopProxy wake covers ControlFlow::Wait while paused.

Key semantics:

  • Resume verbs defer their response until the stop and report a consistent position (pc/frame/vpos/hpos/cck/seconds/retired_instructions); at most one outstanding resume; an optional collect list (regs, memory ranges, digest...) is evaluated atomically at the stop, so one round trip resumes, waits, and gathers.
  • Determinism is preserved: injected input and media changes journal through tt_note_input and the input recorder (--record-input now works headless too), every mutation reports the emulated time it landed, so an interactive control session converts into a replayable .clscript. mem.write is the documented exception (flagged replay_unsafe while time travel is armed).
  • capture.digest/capture.screenshot render through the side-effect-free display path and are identical in both modes -- the cheap change-detection primitive for regression scripts. last_writer exposes the reverse-debug attribution scan (machine parked at the writing instruction, position in the reply).
  • Windowed coexistence: a remote stop pauses without commandeering the local debugger window; a user pause completes a pending resume as user_pause; GUI-driven stops notify the client as event.stopped; the status bar shows a CCP tag while a client is attached; session-owned breakpoints are removed on disconnect, GUI-set points left alone.
  • Auth: generated 128-bit token, announced as one machine-parseable stderr line and via --control-info FILE (0600 JSON) -- the preferred handoff since command lines are visible in ps. Loopback-only by default, same trust model as --gdb.

copperline-ctl (std + serde_json only) wraps the wire protocol for shells: one-shot mode and --repl, built by default (control/ctl-bin features; browser wasm builds compile it out via --no-default-features).

Testing

  • 52 new tests, 1506 total green: proto framing/auth/codec units, executor units against a minimal fixture emulator, 14 headless end-to-end tests over real loopback sockets (step, breakpoint-on-continue, run_until pc+beam, memory round trip, screenshot+digest determinism, save/load state, input journaling, reverse_step + last_writer, pause-interrupts-continue, mid-run inspection, shutdown, collect), and 7 windowed drain tests through the test_app() fixture (stop completion without opening the debugger, run_until frame targets in the burst, user-pause completion, recorder journaling, connect/shutdown).
  • cargo clippy --all-targets and cargo fmt --check clean; --no-default-features (+/- frontend, control) combinations build.
  • Verified live against AROS and Kickstart 3.1 (A1200) driven entirely via copperline-ctl: exact run_until landings, digest stability, last_writer finding the exception-vector install 0.8s into boot, cck-exact save/load state restore, and a windowed session (pause a running machine, step, run_until frame 400, screenshot, clean shutdown). --record-input journaled a control-injected tap as key-after 3.000 0x45 66.

No timing-model changes: STATE_VERSION untouched, no golden-probe re-bless needed.

Known v1 limits (documented)

Single client at a time (GDB-stub convention); mem.write is not a ReplayAction; windowed reverse_continue blocks the GUI during replay; configuration is CLI-only like --gdb (no [control] TOML section); MCP/editor adapters are deliberately out of scope -- they sit on top of this neutral protocol.

A versioned JSON-RPC 2.0 interface over loopback TCP for driving the
emulator from scripts, CI, editors, and AI agents: inspect state, set
breakpoints, resume, rewind, inject input, swap media, and capture the
display within one live session, which the snapshot-once COPPERLINE_DBG_*
environment debugger cannot do and the GDB remote protocol cannot carry.

Two server modes share one typed command surface (src/control/exec.rs),
a thin dispatch over the same ui_*/debug_*/tt_* machinery the debugger
window, console, and GDB stub already share:

- --control ADDR: headless server that owns the Emulator (the gdbstub
  ownership pattern); paused at reset until a client resumes; polls the
  socket once per frame/chunk while running.
- --control-gui ADDR: attaches to the normal windowed session; socket
  threads enqueue over mpsc, the frame loop drains at the top of
  about_to_wait, and an EventLoopProxy wake covers ControlFlow::Wait.

Resume verbs defer their response until the machine stops and report a
consistent position (pc/frame/vpos/hpos/cck/seconds/retired); an
optional collect list is evaluated atomically at the stop. Auth is a
generated token announced on stderr and via --control-info (0600 JSON).
Injected input and media changes journal through tt_note_input and the
input recorder (--record-input now works headless), so a control-driven
session replays deterministically as a .clscript. capture.digest and
capture.screenshot render through the side-effect-free display path and
are identical in both modes; last_writer exposes the reverse-debug
attribution scan. A remote stop pauses the windowed machine without
commandeering the local debugger window, a user pause completes a
pending resume as "user_pause", GUI stops notify as event.stopped, and
the status bar shows a CCP tag while a client is attached.

src/bin/copperline-ctl.rs is a std+serde_json client with one-shot and
--repl modes, built by default (features: control, ctl-bin).

Covered by proto/exec/session unit tests, headless end-to-end tests
over real loopback sockets, and windowed drain tests through the
test_app() fixture; documented in docs/debugger/control.md.
Copilot AI review requested due to automatic review settings July 16, 2026 07:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds the Copperline Control Protocol (CCP): a versioned JSON-RPC 2.0 control surface over loopback TCP for driving the emulator headlessly (--control) or attached to the GUI (--control-gui), plus a bundled CLI client (copperline-ctl). This integrates with existing debugger/time-travel/input journaling machinery so remote sessions can deterministically inspect and steer an emulator instance.

Changes:

  • Introduces the new control module (wire protocol, typed request parsing/execution, session state, headless server, and windowed socket plumbing).
  • Integrates GUI mode control draining into the winit event loop and adds a status bar indicator when a client is connected.
  • Adds extensive tests and documentation for the protocol and client, and wires new CLI flags into copperline.

Reviewed changes

Copilot reviewed 20 out of 21 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/video/window/tests.rs Updates status bar test fixtures and adds windowed control drain tests.
src/video/window/statusbar.rs Adds CCP connection indicator and extends StatusBarView.
src/video/window/control.rs Implements GUI-thread drain/dispatch for windowed control server messages.
src/video/window.rs Integrates control draining into about_to_wait, handles stop completion/notifications, and wires status bar state.
src/main.rs Adds --control/--control-gui CLI args, validation, and headless/server dispatch.
src/lib.rs Exposes control module behind the control feature.
src/gdbstub.rs Exposes shared listen-address normalization for debug/control servers.
src/control/windowed.rs Adds windowed mode socket accept/read/write plumbing and cross-thread message channeling.
src/control/session.rs Adds per-connection session state for breaks and scheduled/journaled input.
src/control/proto.rs Adds JSON-RPC framing/auth gating and payload codecs.
src/control/headless.rs Adds headless control server that owns/drives the emulator session-by-session.
src/control/exec.rs Adds method parsing into typed ops plus core-op execution against the emulator.
src/control.rs Adds top-level control module, config/token plumbing, and shared announce/test emulator helpers.
src/bin/copperline-ctl.rs Adds copperline-ctl CLI client (one-shot + REPL) for the CCP wire protocol.
README.md Documents the new control protocol feature at a high level.
docs/myst.yml Adds the control protocol doc page to the documentation nav.
docs/index.md Links to the new control protocol documentation.
docs/guide/headless.md Documents using CCP as an alternative to fixed scripted flags for headless runs.
docs/debugger/control.md Adds full control protocol documentation and method reference.
Cargo.toml Adds control/ctl-bin features, serde_json optional dep, and wires binaries/features.
Cargo.lock Adds serde_json to the resolved dependency graph.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/control/session.rs
Comment thread src/control/exec.rs Outdated
Comment thread src/control/proto.rs Outdated
Comment thread Cargo.toml
…ature gating

- Normalize break specs at install time to the exact form the machine
  stores key them by (PC/watch through the address-bus mask, watches
  word-aligned, copper breaks masked to an even 24-bit address), so
  break.list attaches ids reliably and removing a copper break added
  with an unnormalized address cannot leak it installed.
- Reject out-of-range beam coordinates and catch vectors instead of
  silently wrapping u32 -> u16, and reject negative or non-finite
  run_until seconds (previously saturated to a cck target of 0 and
  completed immediately); non-finite input.key at_seconds likewise.
- Point the StopEvent reason doc comment at docs/debugger/control.md
  instead of enumerating a value list that had already drifted, and
  make the documented list exhaustive.
- Keep the control feature genuinely optional for the copperline
  binary: CliArgs carries raw --control/--control-gui strings, the
  server config is assembled at the cfg-gated dispatch sites, and a
  build without the feature rejects the flags with a clear error, so
  --no-default-features --features frontend compiles again.
@LinuxJedi LinuxJedi merged commit efc9d79 into main Jul 16, 2026
11 checks passed
@LinuxJedi LinuxJedi deleted the feat/control-protocol branch July 16, 2026 08:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants