Skip to content

ADR 0008 — v1.5.0: DAP onramp, complete CPU ROM coverage, host debug hooks

  • Status: Accepted
  • Release: v1.5.0 (2026-06-11; epics #402 multi-target DAP onramp + complete CPU ROM coverage, #419 host debug hooks)
  • Theme: Derisk the v2.0 TUI-via-DAP architecture, fuzz the CPU against every standard corpus, and expose opt-in hooks a downstream emulator builds a Mesen-class debugger on.

D1 — In-process (zero-marshal) + unix-socket DAP transports

  • Context: the local TUI-as-DAP-client case (v2.0) needs zero-overhead transport; the server was already io.Reader/Writer-based.
  • Decision: unix-socket is just another net.Conn (-dap unix:PATH). The new piece is inproc: all server sends funnel through writeJSON, which consults an optional sink func(any); when set, Response/Event structs go straight to an InprocClient instead of being marshalled. NewInprocServer() + InprocClient.Request dispatch directly. Nil-args requests + all responses round-trip with zero serialization.
  • Consequences: inproc stepIn ≈ 0.34 µs vs ~30 µs over unix (~90×). The foundation for D2.

D2 — TUI panels read via DAP (Registers panel PoC)

  • Decision: the Registers panel renders from m.Regs (RegSnapshot), sourced by Source.Registers() over a single DAP variables round-trip — never direct cpu.CPU field access. LocalSource owns an in-process DAP server (D1) attached to the same CPU/RAM, so local mode reads through DAP too; RemoteSource reuses its wire client. Pattern written up in docs/dap-tui-migration.md.
  • Consequences: proves the v2.0 direction one panel at a time. Migrating the remaining panels (stack/flags/memory/disasm) is deferred to v2.0.

D3 — chippy-state custom live-state event (≤60 Hz)

  • Decision: a server→client custom event pushed during a remote free-run (regs + a reserved dirtyRanges), throttled to 60 Hz in the run loop. The TUI updates m.Regs from it and skips polling when remote+running. Additive per the Mesen/DAP-extension convention — standard clients ignore unknown events.
  • Consequences: a remote run is event-driven, not per-frame polling. Streaming changed memory (dirtyRanges) is the v1.6 follow-up #440.

D4 — Complete CPU ROM coverage; reframe Visual6502 → Tom Harte bus traces

  • Decision: wire every standard 6502 corpus on the download-or-vendor pattern (ADR 0001 D11): AllSuiteA (sha-pinned), Wolfgang Lorenz CPU subset (vendored .prg + KERNAL-trap harness), Klaus interrupt (ca65 port — upstream ships as65 source only, with an active-low/-high feedback-port harness), Tom Harte ProcessorTests (256 opcodes × ~10k cases, state + cycle count). For the Visual6502 "per-cycle bus trace" goal, reframe to comparing chippy's per-cycle bus activity against Tom Harte's cycles field (~100× the coverage, no manual sourcing) — force-enable the per-cycle path (ADR 0004 D2) on VariantNMOS (so decimal stays intact) and record every Bus.Read/Write.
  • Consequences: 228/238 opcodes match bus-exact. Found + fixed a real JSR $20 stack/operand-overlap bug. Skip-listed: 12 JAM/KIL + 6 unstable illegals (state) + 10 per-cycle branch/JSR/RTS bus quirks (#428) + ARR-decimal (#424) + 65C02 (#426) → v1.6. Each corpus is its own CI job.

D5 — Opt-in host debug hooks (epic #419), zero-cost when unset

  • Decision: four hooks let nessy build NES-aware debugging without forking the core, each guarded so the hot path is unaffected when unused:
  • AttachConfig.CustomRequestHandler (ADR 0006) — host DAP requests.
  • cpu.SetAccessHook(func(addr, AccessKind)) — per-byte read/write/exec tracking for a memory heatmap (one nil-check per access; perfgate green).
  • RAM.Freeze(addr, value) / Unfreeze — write-suppress for a debugger freeze (one len check on Write).
  • expr.HostVarResolver (Compile(src, syms, host…)) + Server.SetStopPredicate(func() bool) — host identifiers in conditions (scanline == 30) and host step granularity (run-to-NMI / step-scanline) through the server's pause/ownership model.
  • Consequences: chippy stays a clean general 6502 library; nessy wires the NES semantics on top. All hooks nil-by-default; perfgate stays green.