Release: v1.6.0 (2026-06-16; epic #438 — follow-ups deferred from v1.5.0)
Theme: Close the CPU-accuracy gaps the v1.5.0 corpora surfaced, finish the debugger-UX items deferred from the cc65 .dbg investigation (#390), and complete the DAP streaming story.
Context: the last stable 6502 opcode failing Tom Harte was ARR in decimal mode (skip-listed at v1.5.0).
Decision: add the decimal path to opARR per the 64doc algorithm — N/Z/V from the binary rotate of A & imm; A + C take a per-nibble BCD fixup.
Consequences: 10000/10000 Tom Harte 6b cases pass; 0x6B removed from harteSkip; the decimal-mode arrb probe re-added to the Wolfgang Lorenz suite. Subtle bug caught en route: the high-nibble sum overflows a byte ($F0+$10=256→0), so the comparison must use int.
D2 — 6502 per-cycle bus exactness → 238/238 (#428)¶
Context:TestHarte6502BusTrace passed 228/238; the 10 divergent opcodes (8 taken page-crossing branches + JSR/RTS) were nesCycle-path bus-model gaps (final state + cycle count already matched).
Decision: (1) taken page-crossing branches dummy-read the pre-fixup address (oldPCH | newPCL); (2) JSR $20 gets a dedicated JSRABS addressing mode — resolve fetches only the low operand byte, opJSR pushes the return address then reads the high byte, so the high-byte fetch lands last (silicon order) and the #427 stack/operand overlap quirk falls out for free, collapsing the old NMOS-only re-read special-case and the nesCycle branch into one path; (3) RTS $60 dummy-reads at the pulled PC, not pulled+1.
Consequences: all 238 6502 opcodes bus-exact; harteBusSkip is empty. 65C02 bus trace deferred to v1.7 (#455).
D3 — Tom Harte 65C02 (CMOS variant) + real bug fixes (#426)¶
Context: #401 landed the 6502 NMOS set; the 65C02 set (different data + variant) was deferred.
Decision:TestHarte65C02 runs the wdc65c02 set against VariantCMOS65C02, sharing the harness via a harteSuite descriptor (data subpath + variant + skip + per-case filter). chippy models the WDC 65C02 (implements WAI/STP), so the wdc data set is the match.
Consequences: shook out five real CMOS accuracy bugs, all fixed — ADC-decimal V flag (computed pre-+$60 correction), ASL/ROL/LSR/ROR abs,X cycles (6 +1-on-cross, not flat 7; INC/DEC abs,X correctly stay 7), JMP (abs) (6 not 5), $5C NOP (4 not 8), BBR/BBS (flat 6, no branch penalty). Skips: WAI/STP (halts); invalid-BCD decimal ADC/SBC dropped per-case (effective operand resolved via the production resolve()) — chippy's valid-BCD decimal matches silicon on every case, only the documented-undefined invalid-BCD inputs diverge.
Context: cc65 .dbg carries no struct member layout (V2.18 collapses every csym type to void — verified in ADR 0005), so member trees can't come from debug info.
Decision: user-declared overlay — :watch X as {hp:byte, x:word, y:word} expands into named member rows read at Addr+offset. Members are name:byte|word; offsets auto-advance by width, override with name@N:width. New Watch.Fields []WatchField is an additiveomitempty field, so persistence stays state-schema v1 (golden updated).
Consequences: struct inspection without debug-info support; builds on the array-watch machinery (#408).
Context: the DAP variables handler exposed only Registers + Flags; arrays weren't expandable in VS Code / nvim-dap.
Decision: a Globals scope (advertised only when a .dbg is loaded) enumerates data symbols (sized, or in a data range; code labels filtered). A size>1 symbol expands into indexed byte children via a dynamically-allocated variablesReference (the first dynamic refs — registers/flags stay static constants), paged via start/count with a supportsVariablePaging capability.
Consequences: array/struct inspection in a standard editor, mirroring the TUI watch. setVariable on those children is the v1.7 follow-up (#454).
Context: the chippy-state event (ADR 0008 D3) streamed registers but shipped an empty, reserved dirtyRanges; a remote free-run left memory/disasm panels stale until the stopped full-RAM readMemory.
Decision: during a free-run the run loop arms an AccessWrite hook (cpu.SetAccessHook, #421) stamping a dirty bitmap; each throttled chippy-state flushes coalesced [start,end) spans with their bytes inline (no follow-up readMemory) and clears. A new cpu.AccessHook() getter lets the server chain in front of a host's hook and restore it on stop — zero per-write cost when not running. The TUI applies the spans to its mirror; stopped stays the authoritative full reconcile.
Consequences: remote memory/disasm panels update live. Closes the streaming story opened in ADR 0008 D3.
Context: goreleaser deprecated the brews: (formula) key for pre-built binaries; a goreleaser bump would break the release.
Decision: migrate the tap publish to homebrew_casks: (binaries: [chippy], directory: Casks), with a post-install hook clearing the macOS quarantine xattr (the binary is cosign-signed but not Apple-notarized). Install becomes brew install --cask chippy; the Homebrew section is now macOS-only (Linuxbrew has no cask support; Linux stays on deb/rpm/apk + AUR).
Consequences: release-config future-proofed ahead of a goreleaser bump.