Regenerator 2000 is an interactive 6502 disassembler TUI written in Rust, targeting Commodore 8-bit machines. It uses ratatui + crossterm for the terminal UI and exposes an MCP (Model Context Protocol) server for programmatic access.
The project is split into two main crates to separate logic from presentation:
-
regenerator2000-core: Contains all UI-agnostic logic.analyzer.rs: Auto-analysis (walking code, generating labels).assets.rs: System asset loading (labels, comments, exclude lists).commands.rs: TheCommandenum andUndoStack. Every undoable action is a command.config.rs: System-wide configuration management.core.rs: Central orchestration logic (file loading, command dispatch, integration).cpu.rs: 6502 opcode table and addressing mode logic.disassembler/: 6502 decoding and assembler formatters (64tass, ACME, KickAssembler, ca65).event.rs: Application event types.exporter/: Code for exporting disassembly to ASM or HTML, with roundtrip verification.mcp/: MCP server implementation (stdio and HTTP transports).navigation.rs: Address navigation logic.parser/: File format parsers (.prg, .d64/.d71/.d81, .t64, .crt, .vsf snapshots, .dis65, VICE labels).state/: Persistent application state and project serialization.utils.rs: Shared utility functions.vice/: VICE binary monitor protocol client for live debugging.view_state.rs: View state management.
-
regenerator2000-tui: Contains all terminal UI-related code.ui/: TUI widgets, views (Disassembly, HexDump, Sprites, Charset, Bitmap, Blocks, Debugger), and dialogs.ui_state.rs: Transient UI state (cursor, scroll, active dialog).events/: Input handling and event loop.theme.rs: Color scheme and styling logic.theme_file.rs: Theme file parsing and custom theme support.utils.rs: TUI utility functions.
Addr: A wrapper aroundu16representing a 6502 address.System: Target machine (C64, C128, VIC20, PET, etc.).Assembler: Target assembler syntax (Tass64, Acme, Ca65, Kick).BlockType: How a range of bytes is interpreted (Code, DataByte, DataWord, Address, PetsciiText, ScreencodeText, LoHiAddress, HiLoAddress, LoHiWord, HiLoWord, ExternalFile, Undefined).LabelType: Semantic meaning of a label (Subroutine, Jump, Branch, Pointer, ZeroPagePointer, Field, ZeroPageField, AbsoluteAddress, ZeroPageAbsoluteAddress, ExternalJump, Predefined, UserDefined, LocalUserDefined).
AppState(crates/regenerator2000-core/src/state/app_state.rs): The single source of truth for persistent data. Includesraw_data,block_types,labels,user_side_comments,user_line_comments,undo_stack,settings,cross_refs,bookmarks, andscopes. Serialized to.regen2000proj.UIState(crates/regenerator2000-tui/src/ui_state.rs): Transient state like cursor positions, scroll offsets, and active dialogs. Never serialized.
Regenerator 2000 follows a unidirectional data flow (Redux/Elm style):
- Input:
crates/regenerator2000-tui/src/events/input.rsmaps key events toMenuAction. - Dispatch:
MenuActionis converted into one or moreCommandvariants. - Execution:
Command::apply(&mut AppState)modifies the state and pushes toUndoStack. - Analysis:
AppState::analyze()(viaanalyzer.rs) updates labels and cross-references. - Render:
ui()incrates/regenerator2000-tui/src/ui.rsre-renders the TUI fromAppState+UIState.
All validation, business rules, and state-mutation logic must live in regenerator2000-core (typically as methods on AppState in state/). Both the TUI (regenerator2000-tui) and the MCP server (mcp/handler.rs) are consumers of core logic — they must call shared AppState methods, not re-implement the same checks independently.
For example, if label creation needs to reject duplicate names, that validation belongs in an AppState method (e.g., create_set_user_label_command). The MCP handler and the TUI dialog both call that single method. Never add domain logic directly into mcp/handler.rs or events/input.rs if it can be expressed as a core method.
When adding or modifying a feature, ask: "Would this logic need to be duplicated if a third client (e.g., a GUI, a CLI) were added?" If yes, it belongs in regenerator2000-core.
Specialized tools are available for common tasks. Invoke them via activate_skill:
r2000-analyze-basic: Analyze BASIC programs and mark pointers.r2000-analyze-blocks: Auto-detect and set block types (text, data).r2000-analyze-program: Orchestrate full-program analysis using subagents for blocks, routines, and symbols.r2000-analyze-routine: Trace a subroutine and mark code paths.r2000-analyze-symbol: Find and label all references to a specific address.add-mcp-tool: Templates for adding new programmatic tools to the MCP server.bump-version: Automate version bumping and changelog updates.coding: General Rust coding assistance with project context.code-review: Review changes for idiomatic Rust and project conventions.update-keyboard-shortcuts: Sync keyboard shortcuts across docs and source files.update-mcp-docs: Syncdocs/mcp.mdwith the actual MCP handler tools.verify-mcp: Run MCP integration test suite to verify server functionality.
All AI-generated code MUST follow the rules below. The project enforces them through cargo clippy -- -D warnings; violations will block CI.
Production code must never panic silently.
- Never use
.unwrap()or.expect()outside of#[cfg(test)]blocks. Both are banned byclippy::unwrap_used/clippy::expect_used. - Return
ResultorOptionand propagate errors with?. - Use
anyhow::Resultfor application-level errors where context strings suffice, and typedthiserrorenums for library-facing error surfaces. unreachable!()inside exhaustivematcharms (e.g. an enum variant that logically cannot appear) is acceptable. All other panic macros require a comment explaining why the invariant holds.- Both
lib.rsfiles gate these lints via:Do not remove these attributes.#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::panic))] #![cfg_attr(test, allow(clippy::unwrap_used, clippy::expect_used, clippy::panic))]
- Prefer
?over manualmatch/if letfor error propagation. - Add context with
.context("what was attempted")(fromanyhow) when propagating errors across module boundaries. - Do not silently swallow errors with
let _ = some_fallible_call();. Log or propagate them. - MCP handler functions must return explicit
McpErrorvariants for invalid/missing parameters — seemcp/handler.rsfor the established pattern.
- Add
#[must_use]to every pure function (one with no side effects whose return value carries meaning). This includes constructors (fn new), getters, and any function returningbool, a numeric type, or a wrapped newtype. - Functions that mutate state (
&mut self) and return()do not need#[must_use]. #[must_use]on animplblock annotates every method — use sparingly; prefer per-method annotations.- Run
cargo clippy -- -W clippy::must_use_candidateto find unannotated candidates in existing code.
- All
pubitems inregenerator2000-coremust have a doc comment (///). - Functions returning
Resultmust include a# Errorssection explaining when they fail. - Functions that can panic (outside tests) must include a
# Panicssection. - Use intra-doc links (
[`TypeName`]) to cross-reference types. Runcargo doc --no-depsto verify they resolve. - Run
cargo clippy -- -W clippy::missing_errors_doc -W clippy::missing_panics_docto audit doc coverage.
- Prefer newtypes over raw primitives when a value has a distinct semantic meaning (e.g.
Addr(u16)instead of rawu16). Seestate/types.rsfor the established pattern. - Derive
#[derive(Debug, Clone, Copy, PartialEq, Eq)]for all small value types. AddHashwhen the type is used as a map key,Ord/PartialOrdwhen ordering is needed. - Implement
Displayfor types shown to users; implementFrom/Intoconversions where they remove boilerplate. - Use
Defaultfor structs with sensible zero-values; use#[default]on enum variants rather than a manualDefaultimpl. - Avoid
boolparameters in public APIs; a two-variant enum is self-documenting and prevents argument-order bugs.
- Prefer
&strover&String,&[T]over&Vec<T>, and&Pathover&PathBufin function signatures. - Use
impl Into<String>(orimpl AsRef<str>) for constructor arguments that store aString, asSystem::newdoes. - Avoid unnecessary
.clone()— pass references where ownership is not needed. - When passing closures that only forward to a free function (
|x| f(x)), writefdirectly (clippy::redundant_closure).
- Use
BTreeMap/BTreeSetfor address-keyed maps that must be iterated in order (deterministic output for project files). UseHashMap/HashSetonly when iteration order does not matter and performance is critical. - Prefer
.entry().or_default()overif !map.contains_key() { map.insert() }.
- Run
cargo fmtbefore committing (enforced by pre-commit hook and CI). - Prefer
write!(buf, "{x}")overbuf.push_str(&format!("{x}"))(clippy::format_push_string). - Use
format!capture syntax:format!("{x}")notformat!("{}", x)(Rust 2021+). matcharms that do nothing should use the_ => {}form, not_ => unreachable!()unless you are genuinely asserting the arm is unreachable.
- Every non-trivial function in
regenerator2000-coreshould have at least one unit test in the same file (#[cfg(test)]). - Integration tests live in
tests/and cover cross-module behavior (disassembler output, project serialization, MCP protocol). - Tests may freely use
.unwrap()/.expect()— the per-cratelib.rsallows this under#[cfg(test)]. - Use
AppState::new()(not a manually built struct) as the test baseline; it sets a safe throwaway config path that never touches the real user config.
Lint policy is defined in [workspace.lints.clippy] in the root Cargo.toml. All crates inherit it via [lints] workspace = true in their own Cargo.toml — do not add per-crate [lints.clippy] blocks.
Currently enforced (error in CI via -D warnings):
| Lint | Category |
|---|---|
unwrap_used |
Panic safety |
expect_used |
Panic safety |
panic |
Panic safety |
must_use_candidate |
API hygiene |
missing_errors_doc |
Documentation |
missing_panics_doc |
Documentation |
Aspirational (run manually, apply to new code):
| Lint | Apply when |
|---|---|
needless_pass_by_value |
Writing new functions |
redundant_closure |
Writing closures |
cargo build # Debug build
cargo run -- [file.prg] # Run with optional file
cargo test # Run all tests
cargo fmt -- --check # Check formatting
cargo clippy -- -D warnings # Lint (warnings are errors)- New Block Type: Add to
BlockTypeenum intypes.rs, updateanalyzer.rs, and add a shortcut ininput.rs. - New Assembler: Add to
Assemblerenum, implementFormattertrait indisassembler/formatter_*.rs. - New Command: Add variant to
Commandincommands.rs, implementapplyandundo.
Use present tense, imperative mood (e.g., Add support for D71 disk images). Limit first line to 72 chars.
Never create a git commit automatically after completing a task. Only commit when the user explicitly asks for it (e.g., "commit this", "create a commit"). Staged changes (git add) are acceptable as part of normal workflow, but the final git commit requires an explicit user instruction.
- Unit Tests: Located in
srcfiles (e.g.,cpu.rs). - Integration Tests: Located in
tests/directory. Use these for testing disassembler output, project serialization, and MCP functionality. - Verification: Always run
cargo testandcargo clippybefore finalizing changes.