Debug and trace Atari Lynx games using the Gearlynx emulator MCP server. Provides workflows for 6502 CPU debugging, breakpoint management, hardware inspection, disassembly analysis, and execution tracing. Use when the user wants to debug a Lynx game, trace code execution, inspect CPU registers or hardware state, set breakpoints, analyze interrupts, step through 6502 instructions, reverse engineer game code, examine Mikey/Suzy registers, view the call stack, or diagnose rendering, audio, or timing issues. Also use when the user mentions Atari Lynx development, Lynx homebrew testing, or 6502 debugging with Gearlynx.
Debug and trace Atari Lynx games using the Gearlynx emulator MCP server. Provides workflows for 6502 CPU debugging, breakpoint management, hardware inspection, disassembly analysis, and execution tracing. Use when the user wants to debug a Lynx game, trace code execution, inspect CPU registers or hardware state, set breakpoints, analyze interrupts, step through 6502 instructions, reverse engineer game code, examine Mikey/Suzy registers, view the call stack, or diagnose rendering, audio, or timing issues. Also use when the user mentions Atari Lynx development, Lynx homebrew testing, or 6502 debugging with Gearlynx.
compatibility
Requires the Gearlynx MCP server. Direct tool mode is the default. Before installing or configuring, call debug_get_status to check if the server is already connected. If --mcp-router is enabled, use get_tool_info and execute_tool for routed tools.
metadata
{"author":"drhelius","version":"1.0"}
Atari Lynx Game Debugging with Gearlynx
Overview
Debug Atari Lynx games using the Gearlynx emulator as an MCP server. Control execution (pause, step, breakpoints), inspect the 6502 CPU and hardware (Mikey, Suzy), read/write memory, disassemble code, trace instructions, and capture screenshots — all through MCP tool calls. Hardware documentation is available in the references/ directory.
MCP Server Prerequisite
IMPORTANT — Check before installing: Before attempting any installation or configuration, you MUST first verify if the Gearlynx MCP server is already connected in your current session. In the default mode, call directly. If Gearlynx was intentionally started with , call with , then call with . A valid response from either workflow means the server is active and ready.
debug_get_status
--mcp-router
get_tool_info
{"name":"debug_get_status"}
execute_tool
{"name":"debug_get_status","arguments":{}}
Only if neither workflow is available or the call fails, you need to help install and configure the Gearlynx MCP server:
Installing Gearlynx
Run the bundled install script (macOS/Linux):
bash scripts/install.sh
This installs Gearlynx via Homebrew on macOS or downloads the latest release on Linux. It prints the binary path on completion. You can also set INSTALL_DIR to control where the binary goes (default: ~/.local/bin).
Alternatively, download from GitHub Releases or install with brew install --cask drhelius/geardome/gearlynx on macOS.
Connecting as MCP Server
Configure your AI client to run Gearlynx as an MCP server via STDIO transport. Example for Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json):
Start every session by loading the ROM, confirming it loaded correctly, then checking CPU state and taking a screenshot to understand the current game state. If a .sym, .elf, .lbl, or .noi file exists alongside the ROM, symbols are loaded automatically.
Load additional symbols with load_symbols or add individual labels with add_symbol.
2. Pause and Inspect
Always call debug_pause before inspecting state. While paused:
CPU state: get_6502_status — registers A, X, Y, S, P (flags), PC, interrupt status, MAPCTL visibility
Disassembly: get_disassembly with a start/end address range — only shows executed code paths
Call stack: get_call_stack — current subroutine hierarchy
Memory: read_memory with area name (RAM, Zero Page, Stack, Bank 0, Bank 0A, Bank 1, Bank 1A, BIOS, EEPROM) and address/length
3. Set Breakpoints
Use breakpoints to stop execution at points of interest:
Breakpoint Type
Tool
Use Case
Execution
set_breakpoint (type: exec)
Stop when PC reaches address
Read
set_breakpoint (type: read)
Stop when memory address is read
Write
set_breakpoint (type: write)
Stop when memory address is written
Range
set_breakpoint_range
Cover an address range (exec/read/write)
IRQ
set_breakpoint_on_irq
Stop on specific timer IRQ (Timer0-7: HBLANK, VBLANK, UART, etc.)
Important: Read/write breakpoints stop with PC at the instruction after the memory access.
Manage breakpoints with list_breakpoints, remove_breakpoint, list_breakpoints_on_irq, clear_breakpoint_on_irq.
4. Step Through Code
After hitting a breakpoint or pausing:
Action
Tool
Behavior
Step Into
debug_step_into
Execute one instruction, enter subroutines
Step Over
debug_step_over
Execute one instruction, skip JSR calls
Step Out
debug_step_out
Run until RTS/RTI returns from current subroutine
Step Frame
debug_step_frame
Execute until next VBLANK; use mode: "sync" before dependent calls
Run To
debug_run_to_cursor
Continue until PC reaches target address
Continue
debug_continue
Resume normal execution
After each step, call get_6502_status and get_disassembly to see where you are.
5. Trace Execution
The trace logger records CPU instructions interleaved with hardware events (Suzy math/sprites, Mikey timers/audio/UART, cartridge access).
set_trace_log with enabled: true to start recording (optionally filter event types)
Let the game run or step through code
set_trace_log with enabled: false to stop (entries are preserved)
get_trace_log to read recorded entries
Tracing is essential for understanding timing-sensitive code, interrupt handlers, and hardware interaction sequences.
Hardware Inspection
Mikey (Display, Timers, Audio)
get_mikey_registers — all registers at $FD00-$FDFF, or filter by address
get_lcd_status — verify line-by-line rendering state
Set read/write breakpoints on display buffer addresses to catch corruption source
Analyzing a Subroutine
set_breakpoint at the subroutine entry point
debug_continue → when hit, get_6502_status
Step through with debug_step_into / debug_step_over
After each step: check registers, read relevant memory
add_symbol for the routine and any called subroutines
add_disassembler_bookmark to mark interesting locations
Tracking a Variable
add_memory_watch on the variable's address — watches are visible in the emulator GUI
Set a write breakpoint with set_breakpoint (type: write) on that address
When hit, get_disassembly reveals what code is modifying it
get_call_stack shows the call chain leading to the write
Timing Analysis
set_breakpoint_on_irq for Timer interrupts
set_trace_log with enabled: true to start recording
get_trace_log to see the interleaved CPU + hardware events
Analyze timer reload values via get_mikey_timers
Correlate timer fires with code execution in the trace
Debug Output for Homebrew Development
Homebrew games can send debug text to the Trace Logger via unused Mikey registers $FDC0–$FDC4 — a printf-style mechanism without breakpoints.
set_trace_log with enabled: true and debug_output: true
Run your game — text written through the registers appears in get_trace_log
Register
Write
$FDC0
Flush buffer to Trace Logger (any non-zero value)
$FDC1
Append byte as ASCII character
$FDC2
Append byte as two hex digits
$FDC3
Set string pointer low byte
$FDC4
Set string pointer high byte (triggers copy)
ca65 example:
; Print string + hex value in a single trace entry
; A/X = string pointer, Y = hex value
.proc debug_print
sta $FDC3 ; pointer low
stx $FDC4 ; pointer high (triggers copy)
sty $FDC2 ; append Y as hex
lda #1
sta $FDC0 ; flush
rts
.endproc
lda #<message
ldx #>message
ldy player_hp
jsr debug_print
message: .asciiz "Player HP: "
When disabled (the default), these registers are no-ops — safe to leave in shipping code.
Organizing Your Debug Session
Symbols: Use add_symbol liberally to label addresses you've identified — makes disassembly readable
Bookmarks: Use add_disassembler_bookmark for code locations and add_memory_bookmark for data regions
Watches: Use add_memory_watch for variables you're tracking across steps
Save states: Use save_state / load_state to snapshot and restore emulator state at interesting points
Rewind: Use get_rewind_status + rewind_seek to scrub back through recent execution history without manual save states
Screenshots: Capture visual state with get_screenshot after significant changes
Rewind (Time Travel Debugging)
The emulator continuously records snapshots into a ring buffer during gameplay. You can seek to any recorded snapshot to restore full emulator state at that point in time — like time travel debugging.
Jump to snapshot N (1=oldest, count=newest). Non-destructive — can seek repeatedly
Key Details
Non-destructive seeking: rewind_seek loads a snapshot without removing it. You can seek to the same snapshot multiple times, or jump between different snapshots freely.
Snapshot numbering: Snapshot 1 is the oldest available, snapshot_count is the newest (most recent).
Buffer size: Configured by the user (default: 10 seconds). When full, oldest snapshots are overwritten.
Granularity: Snapshots are taken every N frames (configurable). Default is every frame for maximum precision.
Bug Reproduction with Rewind
Let the game run past the bug occurrence
debug_pause → get_rewind_status to see how far back you can go
Binary search with rewind_seek: try the midpoint, check if the bug is visible (get_screenshot), then narrow the range
Once you find the exact snapshot where the bug appears, inspect CPU/memory state
Set breakpoints at the relevant code, then rewind_seek to a snapshot just before the bug and debug_continue