Skip to content

ADR 0001 — v1.0.0: Foundation

  • Status: Accepted
  • Release: v1.0.0 (2026-05-13; folds in the v0.0.1–v0.4.0 same-week spikes)
  • Theme: A TUI 6502/65C02 emulator + debugger targeting the cc65/ca65 toolchain.

D1 — Variant-based CPU dispatch via a per-CPU opcode-table pointer

  • Context: want NMOS 6502 + 65C02 (and later 2A03/65816) without branching on the variant inside every opcode.
  • Decision: CPU.opcodes *[256]Instr points at a variant table; dispatch is c.opcodes[op]. VariantNMOS is authoritative; OpcodesCMOS is copy-then-override.
  • Consequences: new variants are a new table file, zero hot-path cost. Tables share a base.

D2 — CMOS table init by copy-then-override on Go init() lex order

  • Context: the CMOS table derives from NMOS; illegal opcodes patch NMOS.
  • Decision: rely on Go's file-lexical init() order: opcodes.go < opcodes_cmos.go < opcodes_illegal.go.
  • Consequences: load-bearing invariant — renaming these files breaks the init chain. Documented in CLAUDE.md.

D3 — Step() services interrupts at the instruction boundary, then runs one opcode

  • Decision: NMI (edge) checked first, IRQ (level, gated on !FlagI) second; 7-cycle service pushes PC+P (B clear), sets I, jumps to vector, un-halts. Returns total cycles incl. branch extras via an extraCycles side channel reset each Step.
  • Consequences: simple, deterministic instruction-stepped model. (Per-cycle interleave arrives later for NES — ADR 0004.)

D4 — Faithful NMOS BCD quirk

  • Decision: NMOS ADC/SBC: A and C reflect decimal, but N/V/Z reflect the parallel binary path (real-silicon quirk). CMOS: N/V/Z reflect the decimal result, +1 cycle. Dispatched via c.Variant.
  • Consequences: passes the Bruce Clark exhaustive BCD sweep; matches Klaus.

D5 — Layered bus chain, loader/reset bypass MMIO

  • Decision: CPU → tui.WBus → cpu.MMIO → cpu.RAM. MMIO dispatches registered Peripherals first, falls back to RAM. The loader + reset-vector helpers write RAM.Data directly, bypassing MMIO.
  • Consequences: peripherals must sit at addresses no ROM occupies; debugger pokes / loads don't trigger peripheral side effects.

D6 — Peripherals as MMIO registers (Apple-1 style)

  • Decision: TextOutput @ $F001 (write → buffer, rendered as a panel); KeyboardInput @ $F004/$F005 (data/status pair). cpu.Peripheral interface: Range/Read/Write.
  • Consequences: generic I/O surface; the TUI pushes keys, the CPU drains them.

D7 — Execution trace via an optional hook + replay

  • Decision: cpu.Tracer interface (LogStep/LogInterrupt) called from Step; FileTracer is a buffered sink. -trace PATH / :trace. trace.Replay parses a log into navigable frames (replay mode).
  • Consequences: post-mortem analysis without re-running; foundation for later replay search/diff (ADR 0005).

D8 — TUI on Bubble Tea + Lipgloss; every key path returns a tea.Cmd

  • Decision: Elm-architecture model in internal/tui (private — chippy-specific). Panels read CPU+RAM directly. Keep Update responsive.
  • Consequences: keeps rendering pure; the later DAP migration (ADR 0008) reshapes this without changing the framework.

D9 — cc65/ca65 as the first-class toolchain

  • Decision: parse cc65 .dbg for the symbol table + PC→source map (symbols package); the loader runs ld65 for unlinked .o.
  • Consequences: source-level debugging of ca65/cc65 programs. (Limitation surfaced later: .dbg carries no C struct/array layout — ADR 0005 D3.)

D10 — Reverse-step via page-level copy-on-write RAM shadow + snapshot ring

  • Decision: RAM keeps an optional CoW page shadow; each step pushes a Snapshot (page delta + regs) to a fixed SnapshotRing. < pops/restores.
  • Consequences: cheap reverse-step (hundreds of bytes/step). (Depth extended to millions of steps via keyframes in ADR 0005 D5.)

D11 — GPL test ROMs are downloaded + sha256-verified, never vendored

  • Decision: MIT license for chippy; Klaus's GPL 6502_65C02_functional_tests fetched on demand into the user cache, sha256-pinned. Klaus functional + 65C02 + Bruce Clark BCD are the pre-1.0 correctness baseline.
  • Consequences: clean license boundary; the pattern scales to AllSuiteA / Tom Harte / Lorenz (ADR 0008).

D12 — Persistence: frozen state-format v1 contract

  • Decision: ~/.chippy/state-<rom>.json carries schemaVersion: 1; new fields stay optional within v1.x; semantic changes require a version bump + migration. A golden test pins state-v1.json.
  • Consequences: save-state stability across releases; new features add omitempty fields (watches, deep-rewind budget, …).

D13 — Single binary, goreleaser, bare vX.Y.Z tags

  • Decision: one chippy binary; goreleaser publishes signed binaries + a Homebrew tap on tag push.
  • Consequences: simple release story; homebrew-core deferred until ~30 stars.

D14 — Breakpoint/watch sigils mirror nvim-dap

  • Decision: 🛑 bp · 👉 PC · 🔶 conditional · 💩 rejected · 📜 logpoint · 👁/✏/🔁 read/write/r+w watch. Wide-emoji column compensation keeps the address column aligned.
  • Consequences: familiar to nvim-dap users.