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.
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.)
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.
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.
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).
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.