Skip to content

ADR 0005 — v1.3.0: Debugger UX polish

  • Status: Accepted
  • Release: v1.3.0 (2026-06-04; epic #396)
  • Theme: Round out the TUI debugger — conditions, memory editing, watch expansion, trace navigation, deep rewind.

D1 — Conditional breakpoints over DAP

  • Decision: forward condition / hitCondition / logMessage on setInstructionBreakpoints; RemoteSource maps the TUI's SourceBP{PC,Cond,HitLimit,Log} to the DAP fields; a BP-modal c keybind edits conditions inline.
  • Consequences: the server short-circuits non-matching hits without a TUI round-trip; conditions evaluate through the shared expr package.

D2 — :mem command + memory writes through the bus

  • Decision: add :mem $ADDR V [V…]; route TUI memory writes through the WBus/MMIO chain (not raw RAM) so watchpoints + MMIO side effects fire.
  • Consequences: debugger pokes behave like CPU writes where it matters.

D3 — Watch array expansion + the cc65 .dbg finding

  • Context: wanted struct/array expansion in the watch panel from .dbg type info.
  • Decision (empirical): verified cc65 V2.18 .dbg carries no struct member layout or array bounds — every csym type collapses to void. So scope to array-only best-effort: :watch X xN pins N consecutive byte/word elements; auto-seed from sym size= when present. Manual struct overlay + DAP array children deferred (#409/#410).
  • Consequences: honest scope; the finding is recorded (reference-cc65-dbg-no-types) so it isn't re-litigated. Watch.Count is an optional v1 state field (no schema bump).

D4 — Trace replay: search / jump-to-cycle / side-by-side diff

  • Decision: :find EXPR / :rfind evaluate the breakpoint expr grammar against a scratch CPU per frame (bare ===); :cycle N binary-searches the monotonic cycle column; -diff PATH loads a second trace and trace.Diff marks the first divergence; d/D toggle a side-by-side overlay.
  • Consequences: pure-trace logic stays unit-testable apart from the TUI; high-fidelity post-mortem comparison of two runs.

D5 — Deep rewind via keyframes + :rewind-budget

  • Context: the per-step snapshot ring (ADR 0001 D10) only reaches back its capacity (~256 steps).
  • Decision: cpu.KeyframeRing keeps periodic full-RAM snapshots (every 4096 steps); :rewind N restores the nearest keyframe ≤ target and replays forward to the exact step. :rewind-budget MB caps keyframe memory (reach = budget/64KiB × interval); memory is a ceiling, not a reservation.
  • Consequences: reach jumps from hundreds to millions of steps; a deep rewind replays ≤4096 instructions (~1 ms). Forward-replay assumes deterministic execution between keyframes (buffered input is snapshotted).