Agent skill

C64 Meatloaf Debug

by idolpx in idolpx/meatloaf

Debug Commodore 64 BASIC programs, cc65-compiled C PRGs, and Meatloaf ESP32 firmware in tandem, using the Ultimate 64's REST API and Meatloaf's UART serial debug output.

GPL-3.0Auto-check passedDevelopment

Install C64 Meatloaf Debug

skills CLI
$ npx skills add idolpx/meatloaf --skill c64-meatloaf-debug -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install idolpx/meatloaf c64-meatloaf-debug --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/idolpx/meatloaf.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.claude/skills/commodore64-debugging .claude/skills/c64-meatloaf-debug && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
c64-meatloaf-debug
GitHub stars
128
Token cost
~12k tokens
SKILL.md length
4,719 words
Files
5 (incl. scripts)
Skills in repo
6
Repo updated
First seen
Licence
GPL-3.0

At a glance

Debug Commodore 64 BASIC programs, cc65-compiled C PRGs, and Meatloaf ESP32 firmware in tandem, using the Ultimate 64's REST API and Meatloaf's UART serial debug output.

  • Works in 11 steps: Setup → Identify what changed → Run and observe → …
  • The user mentions debugging C64/Meatloaf interactions
  • SKILL.md covers Architecture, Skill Components, Robust Serial Capture and Keystroke Injection (Keyboard…, plus 2 more sections
  • Runs Python scripts from its folder; calls python3, pip and python; needs U64_PASSWORD

What it does

C64 Meatloaf Debug is an agent skill from idolpx/meatloaf. Debug Commodore 64 BASIC programs, cc65-compiled C PRGs, and Meatloaf ESP32 firmware in tandem, using the Ultimate 64's REST API and Meatloaf's UART serial debug output. Automates the full unattended cycle: write/modify BASIC, C, or C++ → deploy (inject BASIC, write PRG to native load address, or pio build+flash) → run on real hardware → read screen + serial logs → diagnose → fix → repeat. Use whenever the user mentions debugging C64/Meatloaf interactions, Meatloaf firmware issues, C64 BASIC or cc65 C networking…

Its SKILL.md is about 12k tokens, which your agent loads only when the skill is triggered. The skill folder holds 6 other files, including scripts (for example `evals/evals.json`, `scripts/meatloaf_debug.py` and `scripts/pio_config.py`).

It sits in Development, covering Debugging, Embedded systems and REST APIs. It works with ESP32 and C++. The repository describes itself as: The internet is a massive floppy drive for your C64 with Meatloaf! The licence is GPL-3.0.

When your agent uses it

  • The user mentions debugging C64/Meatloaf interactions
  • Meatloaf firmware issues
  • Cc65 C networking
  • POST/PUT body problems

Example prompts

  • “s REST API and Meatloaf”
  • “unattended debug cycle”
  • “/c64-meatloaf-debug”

Requirements

  • Python 3

Workflow steps

11 steps, taken from the step headings in SKILL.md.

  1. Setup
  2. Identify what changed
  3. Run and observe
  4. Read two data sources (plus Meatloaf Console)
  5. Diagnose
  6. Fix
  7. Release the hardware
  8. POST Body Not Sent (Content-Length: 0)
  9. Response Body Not Visible on C64
  10. Buffer Truncation (Duplicated m_len += got)
  11. Q2: Second POST Uses Cached State

What it can do on your machine

Read from SKILL.md and the folder at commit 9a88a9f. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Ships 3 files in scripts/ (Python), which the agent can run.

    Shell commands in SKILL.md call:

    • python3
    • pip
    • python
    • curl
    • wget

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    Links to these hosts (documentation or services it may open):

    • csdb.idolpx.com

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names these keys or tokens, usually read from environment variables:

    • U64_PASSWORD

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

C64 Meatloaf Debug loads about 12k tokens when it runs. Until then it costs about 254 tokens; SKILL.md has 4,719 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~254
When it runs · the whole SKILL.md, loaded when a task matches
~12k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); the scripts in this folder are not scanned.

SKILL.md

The full file from idolpx/meatloaf at commit 9a88a9f, republished under its GPL-3.0 licence (© idolpx). 4,719 words, ~11,675 tokens.

Download SKILL.mdSave it as .claude/skills/c64-meatloaf-debug/SKILL.md (or your agent's skills folder). This skill also uses 4 other files; get the full folder from GitHub.
name
c64-meatloaf-debug
description
Debug Commodore 64 BASIC programs, cc65-compiled C PRGs, and Meatloaf ESP32 firmware in tandem, using the Ultimate 64's REST API and Meatloaf's UART serial debug output. Automates the full unattended cycle: write/modify BASIC, C, or C++ → deploy (inject BASIC, write PRG to native load address, or pio build+flash) → run on real hardware → read screen + serial logs → diagnose → fix → repeat. Use whenever the user mentions debugging C64/Meatloaf interactions, Meatloaf firmware issues, C64 BASIC or cc65 C networking, POST/PUT body problems, PETSCII encoding, U64 remote control, cc65 symbol table / map file / VICE label file debugging, or any "unattended debug cycle" for the C64 → Meatloaf → API pipeline. Also trigger when the user talks about the Ultimate 64 REST API, serial capture from an ESP32, automated C64 program testing, or uploading compiled .prg files. This is the primary skill for anyone doing C64 HTTP debugging with Meatloaf — do not defer to other skills for these scenarios.

C64 / Meatloaf Unattended Debug Cycle

This skill guides the full iterative loop for debugging C64 BASIC programs that use Meatloaf's Full-Mode HTTP Client:

┌──────────────────┐    IEC serial   ┌──────────────┐   HTTP   ┌─────────────┐
│  Ultimate 64     │ ◄──────────────►│  Meatloaf    │◄────────►│  API /      │
│  (BASIC program) │                 │  (ESP32)     │          │  Test Svr   │
└──────────────────┘                 └──────┬───────┘          └─────────────┘
                                            │ USB serial (monitor_speed)
                                            ▼
                                     ┌──────────────┐
                                     │  Serial      │
                                     │  capture     │
                                     │  (this PC)   │
                                     └──────────────┘

Two codebases are debugged in the same loop:

  • BASIC — runs on the C64, talks to Meatloaf via IEC bus
  • Meatloaf C++ — the ESP32 firmware in this repo (the directory holding platformio.ini; MEATLOAF_DIR overrides it)

Which one to fix? Claude decides based on evidence from the screen and serial log. The skill provides tools to gather evidence and deploy fixes to either side.


Architecture

Ultimate 64 REST API

The Ultimate 64 exposes a REST API on port 80. Key endpoints:

EndpointMethodPurpose
/v1/machine:resetPUTHardware reset
/v1/machine:writememPUTWrite hex bytes (≤128 bytes)
/v1/machine:writememPOSTWrite binary (unlimited, for BASIC programs)
/v1/machine:readmemGETRead binary from memory
/v1/infoGETSystem info
/v1/machine:menu_buttonPUTToggle menu
/v1/machine:debugregGET/PUTRead/write debug register ($D7FF)

Memory locations used:

  • $0277 — Keyboard buffer (10-byte FIFO). Key codes go here.
  • $00C6 — Keyboard buffer count. Set after writing to $0277.
  • $0400 — Screen memory. 40×25 = 1000 bytes. READ that.
  • $0801 — BASIC program start. POST tokenized BASIC here. C PRGs use this address too by default (cc65 EXEHDR at $0801).
  • $002D-002E — Variable pointer. BASIC-only — set to end of BASIC program after injection. Do NOT touch for C PRGs (clobbers the zero-page globals cc65 set up).
  • $D7FF — U64 debug register. Single free byte in the cartridge expansion area. Useful as a runtime checkpoint signal from C code (see "cc65 Debug Options" below).
  • $DD00-$DD03 — CIA#2. $DD00 is the IEC serial bus (ATN/CLK/DATA), $DD01/$DD03 the user port data and direction used by parallel speeders (DolphinDOS, SpeedDOS), $DD02 the Port A direction. Decoded by the bus command — see "F. IEC bus and user port signals".
  • $DC0D — CIA#1 interrupt control/status. Bit 4 is the /FLAG pin, where the IEC SRQ line arrives (shared with the datasette input). Reading it is destructive on a 6526 — it clears latched interrupt flags.

Full register reference: https://csdb.idolpx.com/tools/mem64

Meatloaf Source Structure
FilePurpose
platformio.iniBuild config, verbose flags
lib/network/http.cppFull-mode HTTP client implementation
lib/network/http.hHTTP class declarations
lib/device/iec/drive.cppIEC routing to MStreams (bug location: m_len)
test/http/test_server.pyEcho server on port 8080
test/http/openai_chat_client.basExample BASIC chat client
test/http/http_full_client_test.basTest suite
Build Configuration

All build settings are read from the project's platformio.ini — never hardcode them, and never assume the values below. The board, serial port and baud rate change per machine and per user; platformio.ini is where the project documents them, so it is the single source of truth.

Ask for the resolved settings before doing anything that touches the device:

bash
python3 scripts/meatloaf_debug.py config
json
{
  "project_root": "/Users/you/src/meatloaf",
  "ini_path": "/Users/you/src/meatloaf/platformio.ini",
  "environment": "fujiloaf-rev0",
  "board": "esp32-wrover-16m",
  "mcu": "esp32",
  "flash_size": "16m",
  "u64_ip_address": "192.168.1.176",
  "upload_port": "/dev/cu.usbserial-14520",
  "serial_port": "/dev/cu.usbserial-14520",
  "monitor_speed": "2000000",
  "verbose_flags": [],
  "defines": ["BUILD_IEC", "ENABLE_CONSOLE", "SD_CARD", "..."],
  "overrides": {}
}
SettingWhere it comes from
Active env[meatloaf] environment (falls back to [platformio] default_envs)
Board / MCU / flash size[env:<environment>] board, then boards/<board>.json
Upload port[env:<environment>] upload_port, else [env] upload_port
Serial capture portmonitor_port if set, else upload_port
Serial debug baudmonitor_speed
Verbose flagsactive -D flags in the env's build_flags (commented-out flags are correctly ignored)
Ultimate 64 address[meatloaf] u64_ip_address (debug tooling only — not compiled into firmware)

scripts/pio_config.py does the parsing (${section.key} interpolation and ; comments included) and is imported by both meatloaf_debug.py and serial_capture.py. The project root is found by walking up from the script, so the skill works from any working directory.

Checking verbose_flags matters. If the serial log is missing the HTTP diagnostics this skill's tables refer to, VERBOSE_HTTP is probably not enabled — it ships commented out. Uncomment it in platformio.ini under [env] build_flags and reflash, rather than concluding the code path was never reached.

To target a different board or port for one run without editing platformio.ini, set the MEATLOAF_* env vars (see "Environment Variables").


Skill Components

commodore64debugging/
├── SKILL.md                            ← This file: workflow + reference
├── scripts/
│   ├── meatloaf_debug.py               ← Python automation + CLI
│   ├── pio_config.py                   ← Reads build settings from platformio.ini
│   └── serial_capture.py               ← Robust serial capture daemon
└── evals/
    └── evals.json                      ← Test cases

### BASIC Compilation

**Only bc64 is supported** for BASIC compilation. The built-in BASIC V2
tokenizer has been removed — all `.bas` files go through the `bc64` CLI:

```bash
bc64 -a -o /tmp/output.prg source.bas

The 2-byte PRG load address header (01 08) is stripped automatically by inject_basic(), which POSTs the raw tokenized body directly to $0801.

bc64 features used across all test programs:

  • @alias variables for unlimited-length names (via -a flag)
  • Labels for control flow (Start:, ShowMenu:, HttpFetch)
  • Auto-numbering (no manual line numbers needed)
  • Case-insensitive keywords
C Compilation (cc65)

C programs use the cc65 toolchain (cl65, ca65, ld65). Deploying a .prg requires: reset → upload body-only to $0801 → type RUN. The full protocol is in the "C-PRG / cc65 Programs" section below.

Recommended build flags:

bash
cl65 -t c64 -O -I. --mapfile foo.map -o foo.prg foo.c
# Debug build: add --listing foo.lst and -g (skip -O)

The --mapfile is essential — see "cc65 Debug Options" below.

Python Modules

scripts/meatloaf_debug.py — The main automation module. Use it for every operation. Run commands via CLI or import as a Python module.

scripts/serial_capture.py — A self-contained daemon for reading Meatloaf's UART debug output. Does not require meatloaf_debug.py to run — it's a standalone script.

Python dependencies

Both scripts need requests (U64 REST API) and pyserial (capture daemon).

A modern system Python is usually PEP 668 "externally managed", so pip install --user requests pyserial fails outright. Use a venv and call its interpreter explicitly rather than python3:

bash
python3 -m venv /tmp/c64dbg && /tmp/c64dbg/bin/pip install requests pyserial
/tmp/c64dbg/bin/python scripts/meatloaf_debug.py config

Symptom if you skip this: Error: 'requests' library required. pip install requests from meatloaf_debug.py, or ModuleNotFoundError: No module named 'serial' from the capture daemon.


Robust Serial Capture

The serial capture is the most important diagnostic tool. It reads the ESP32's Debug_printv() output over USB UART, on the port and baud rate resolved from platformio.ini (monitor_port / monitor_speed — run meatloaf_debug.py config to see them).

Why It's Robust
FeatureWhat it does
Auto-reconnectIf the port disappears (ESP reboot, USB unplug, flash cycle), it retries with exponential backoff (1s → 2s → 4s → ... → 30s max)
HeartbeatWrites a timestamp every time data arrives. The health check uses this to detect a dead capture within 30 seconds
PID trackingWrites its PID to a file so the management script can find and kill it reliably
Graceful shutdownHandles SIGTERM/SIGINT to close the log file cleanly
Stale cleanupstart_capture() always kills the previous instance first, then also runs pkill -f serial_capture.py as a backup
Non-exclusive portOpens with exclusive=False so monitoring tools can coexist
Strips ANSI/control charsProduces clean readable log output
Timestamped markersWrites === CAPTURE STARTED/STOPPED <timestamp> === lines
What survives
  • ESP32 crash/reboot — the capture detects the port error, enters backoff mode, and reconnects automatically when the port reappears.
  • USB cable disconnect — reconnects with backoff when cable goes back in.

What does not survive:

  • Flashing firmware — stop the capture first. esptool needs the port to itself on every platform, not just Windows (see "When to stop it").
When to start it

Start capture before running any tests:

bash
python3 scripts/meatloaf_debug.py capture start

After flashing firmware, wait for the ESP32 to boot and start logging:

bash
python3 scripts/meatloaf_debug.py capture wait --timeout 20

This polls until new data appears in the serial log (up to 20s timeout).

When to stop it

Stop the capture when the debug session ends — it holds the serial port open, and on Windows that port is exclusive, so nothing else can attach until it lets go:

bash
python3 scripts/meatloaf_debug.py capture stop

Leaving it running blocks the two things a person reaches for next: their own serial monitor (pio device monitor, PuTTY, the IDE terminal), and the next flash.

Flashing fails while the capture holds the port — on every platform. The error differs but the cause is the same:

PlatformWhat esptool reports
WindowsCould not open COM7, the port is busy / PermissionError(13, 'Access is denied.')
macOS / LinuxA serial exception error occurred: device reports readiness to read but returned no data (device disconnected or multiple access on port?)

The macOS/Linux message is misleading — it reads like a cable or driver fault, and esptool's own note points you at the hardware. It is neither. The port is shared there, so the capture keeps reading bytes out from under esptool's Connecting.... handshake and the sync never completes. Verified on macOS 2026-08-19: build --flash failed this way with the capture running, and the identical pio run -t upload succeeded seconds later once it was stopped.

So the flash sequence is always: stop capture, flash, start capture. And the last thing any session does is stop it.

Health check
bash
# Quick check (exit code 0 = running)
python3 scripts/meatloaf_debug.py capture status
# Or independently:
python3 scripts/serial_capture.py --check /tmp/serial_capture.pid
Reading the log
bash
# Last 50 lines
python3 scripts/meatloaf_debug.py capture tail

# Grep for specific patterns
python3 scripts/meatloaf_debug.py capture grep --pattern "perform OK"

# Read raw log
cat /tmp/meatloaf_serial.log

Keystroke Injection (Keyboard Buffer)

Sending keys to the C64 uses the keyboard buffer at $0277, a 10-byte hardware FIFO. This is the only way to inject keystrokes on the U64.

Why not CIA registers? The U64 API docs explicitly state "Writing to the I/O registers of the 6510 is not possible", which blocks direct CIA #1 ($DC00/$DC01) keyboard matrix manipulation. The keyboard buffer at $0277 is the sole injection path.

How it works
  1. Flush stale keys from the buffer by zeroing $00C6 first
  2. Write ≤8 PETSCII key codes to $0277 (leaving margin in the 10-byte FIFO)
  3. Set the count at $00C6 — this triggers the KERNAL IRQ to read the buffer
  4. Poll $00C6 until it returns to 0 (C64 has consumed the keystrokes)
  5. Write the next chunk
  6. If drain times out: flush stale keys, retry in 1-byte chunks
Robustness features
FeatureWhat it does
Flush before writeZeroes $00C6 before every new batch to discard stale keys from previous operations
Write-then-setWrites key codes to $0277 before setting $00C6, so the IRQ never sees empty buffer slots
Drain pollingPolls $00C6 between chunks. The C64 processes keys at IRQ rate (~60Hz), so each chunk takes ~1/60s
Bogus count guardIf $00C6 reads ≥10 (buffer overflow indicator), force-flushes immediately
1-byte fallbackIf a chunk fails to drain within 3s, switches to 1-byte-at-a-time mode to minimize per-write risk
Panic STOP ×3press_stop() sends RUN/STOP 3 times + ENTER — the KERNAL checks for STOP during I/O at specific points
Known limitations
  • Blocking I/O blocks the IRQ — if the C64 is stuck in KERNAL IEC transfer (LOAD/SAVE/OPEN), the keyboard IRQ won't fire and the buffer won't drain. press_stop() sends STOP repeatedly because the KERNAL checks for RUN/STOP at specific I/O polling points.
  • No feedback — there's no way to confirm a specific key was processed beyond polling $00C6 (you can't tell which key was consumed).
  • 10-byte FIFO is small — long text strings require multiple chunks with drain waits between.
  • NMI/RESTORE not accessible — the RESTORE key (CPU /NMI pin) could theoretically unstick even KERNAL-blocked I/O, but the U64 API has no mechanism to trigger it (no CIA or NMI endpoints).

C-PRG / cc65 Programs

A .prg file (cc65, KickAss, raw binary with a 2-byte LE header) is uploaded very differently from a BASIC program. The existing inject_basic() is BASIC-only — it always writes to $0801 and pokes the BASIC variable pointer at $002D. Both assumptions are wrong for C.

Why BASIC and C differ
AspectBASIC (bc64)C (cc65)
Load addressAlways $0801Whatever the linker chose ($0801 for cc65 default, but -S addr can move it)
Variable pointer at $002DRequired (BASIC state)Must NOT be touched (C uses zero page for its own globals)
Header size0 bytes (bc64 strips it)2 bytes (LE load address)
Program sizeSmall (<2 KB)Often 10–40 KB (must use POST, not PUT)
Auxiliary dataNoneSprite patterns, lookup tables, char sets at fixed addresses
Entry mechanismRUNSYS <addr> (decimal or hex)
The cc65 PRG layout (default cl65 -t c64)
$0801  EXEHDR (8 bytes: jump to STARTUP at $080D)
$080D  STARTUP (zero page init, runtime init, call main)
$0840  CODE (cc65 runtime + your functions)
       ...
       RODATA (string literals, lookup tables, sin/cos)
       DATA (initialised globals)
       INIT (run once, then freed for BSS)
$5415  BSS (zeroed globals; ONCE constructors run, then this region)
$5A00  <-- your memory is free above here for sprites, bitmap, etc.

cc65's --start-addr (or -S) default for -t c64 is $0801, so the PRG header shows 01 08 (little-endian). That's why the first bytes of c64u_radar.prg are 01 08 0B 08 ... — load at $0801, EXEHDR contains 0B 08 = $080B = jump to STARTUP at $080D (the +2 accounts for the JSR instruction's own size).

Injecting a cc65 C PRG

The inject-prg command does not exist in this version of meatloaf_debug.py. Use the raw POST to writemem instead.

The correct deploy sequence — always:

bash
# 1. Reset the U64 (clears RAM)
python3 scripts/meatloaf_debug.py reset
sleep 4                      # wait for boot + READY prompt

# 2. Upload PRG body (strip the 2-byte load-address header)
U64_IP="${U64_IP_ADDRESS:-192.168.1.176}"
python3 -c "
import requests
with open('/path/to/your.prg', 'rb') as f:
    body = f.read()[2:]                    # strip LE load-address header
r = requests.post(f'http://${U64_IP}/v1/machine:writemem?address=0x0801',
                  data=body, timeout=10)
print(f'Upload {len(body)} B -> {r.status_code}')
"

# 3. RUN (type keystrokes via keyboard buffer)
python3 scripts/meatloaf_debug.py type "RUN"
sleep 3
python3 scripts/meatloaf_debug.py screen   # verify output

Why reset first? The POST writemem appends to existing RAM content, it doesn't clear it. Stale BASIC program bytes from earlier runs corrupt the linker's EXEHDR. A hardware reset gives a clean $0801-$7FFF space.

Why strip the header? The .prg file starts with a 2-byte little-endian load address (01 08 = $0801). The POST writemem writes bytes directly into RAM starting at the given address — if you POST the header bytes too, they end up as program data at $0801-$0802, corrupting the BASIC stub.

How the upload works
  1. Read the .prg file in binary mode.
  2. Skip the first 2 bytes (LE load address).
  3. POST the remaining bytes to writemem?address=0x0801.
  4. The PRG's EXEHDR (BASIC SYS stub) at $0801-$080C handles RUN → SYS2061.
  5. cc65's STARTUP runs at $080D, calls main().

Do NOT touch the BASIC variable pointer at $002D. C programs use the zero page for their own globals; clobbering it corrupts state.

Auxiliary data uploads (sprites, tables, char sets)

Many C programs reference pre-built data at known addresses (sprite patterns at $5A00, lookup tables generated by Python at $7000, etc.). The PRG doesn't include them; you upload them separately:

bash
# Upload sprite patterns to the standard sprite area
python3 scripts/meatloaf_debug.py write-mem 5A00 sprites.bin

# Upload a sin/cos table generated by a Python tool
python3 scripts/meatloaf_debug.py write-mem 7000 sincos.bin

# Read back what you wrote (to verify)
python3 scripts/meatloaf_debug.py read-mem 5A00 512 > /tmp/sprites_readback.bin
diff <(xxd -p sprites.bin) <(xxd -p /tmp/sprites_readback.bin)

write-mem and read-mem are arbitrary-address, arbitrary-length wrappers around the same writemem/readmem REST endpoints used by the BASIC path.

Common pitfalls
SymptomCauseFix
?SYNTAX ERROR after RUNPRG header bytes (01 08) were POSTed as data instead of strippedStrip the 2-byte header before POSTing the body to $0801
Screen garbage, then READY.PRG clobbered zero page / BASIC vectorsDon't poke $002D-$002E for C PRGs
READY. with no obvious effect after RUNProgram loaded but main() returned or fell through to STOP. Check your C source's startup codeWrap infinite loop in main(), or verify with /help
Reset didn't fix garbageReset doesn't clear RAM — stale data remains at $0801Always do a hardware reset (reset endpoint, wait 4s) before uploading

cc65 Debug Options

The cc65 toolchain ships with several outputs that are pure gold for debugging — far more useful than just the .prg.

OptionWhat it producesWhen to use it
--mapfile foo.mapFull link map with all segment addresses, sizes, exportsAlways — it tells you exactly where every symbol lives
-Ln foo.lblVICE label file (one label per line)Load into VICE monitor: source foo.lbl
-Wl --dbgfile,foo.dbgVICE/CCS64 debug infoSource-level stepping in VICE
--listing foo.lstAssembler listing interleaved with C sourceWhen you suspect compiler codegen bugs
-O vs -OirOptimisation level (none / inline-runtime / inline-known)-Oir for final, no flags for debugging (preserves variables)
-Wl -vmVerbose map with cross-referenceWhen --mapfile output isn't enough
The map file in practice
text
$ grep -E "(__MAIN|__BSS|__STACK|HIMEM|EXEHDR|STARTUP)" foo.map
Exehdr                000801  00080C  00000C
Startup               00080D  00083F  000033
__MAIN_START__        00080D RLA    __MAIN_SIZE__     00C7F3 REA
__BSS_RUN__           005415 RLA    __BSS_SIZE__      000031 REA
__STACKSIZE__         000800 REA    __HIMEM__         00D000 REA

The columns: Name, Start, End, Size, Align. The suffixes mean:

  • RLA = relocatable address (resolved at link time) — absolute C64 address after linking
  • REA = relocatable expression (might be a constant, not an addr)
  • RLZ = relocatable address in zero page
  • LL = linker-generated local
Debugging without VICE: the U64 debug register

The U64 exposes a machine:debugreg endpoint that reads/writes a single byte at $D7FF. This is the cartridge expansion ROM/RAM area; cc65 doesn't use it, so it's a free scratch byte for runtime assertion logging:

c
// In your C code:
#define DBG_REG (*(volatile unsigned char *)0xD7FF)

// At a checkpoint:
DBG_REG = 0x42;   // marks "I reached the JSON parser"

// On error:
DBG_REG = 0xEE;   // marks "fatal: out of memory"

Read it from the host:

bash
python3 scripts/meatloaf_debug.py read-mem D7FF 1
# → 42 if the program reached your checkpoint

Combine with the U64 REST debugreg endpoint for an even faster poll without going through readmem:

bash
curl http://192.168.1.176/v1/machine:debugreg
Building for debug vs release
Stagecl65 flags
Development(no -O), --mapfile, --listing, -Wa -l for asm listing
Profiling-Oir (optimise + inline known funcs), --mapfile to confirm sizes
Release-Oir -Os, strip assertions, set __HIMEM__ carefully

For the c64u-radar build (/home/qus/dev/_c64/c64u-radar/c64u_radar/Makefile):

sh
# Debug:
cl65 -t c64 -g --mapfile foo.map --listing foo.lst -o foo.prg foo.c

# Release:
cl65 -t c64 -Oir -Os --mapfile foo.map -o foo.prg foo.c

Quick Reference: All Commands

C64 Control
bash
# Read screen (40×25 PETSCII screen codes decoded to ASCII)
python3 scripts/meatloaf_debug.py screen

# Inject BASIC from file (- = stdin)
python3 scripts/meatloaf_debug.py inject test/http/my_test.bas

# Type a BASIC command and read screen
python3 scripts/meatloaf_debug.py run "RUN"
python3 scripts/meatloaf_debug.py run "LIST 100-200"
python3 scripts/meatloaf_debug.py run "PRINT PEEK(53280)" --wait 1

# Type text (no screen read, for interactive programs)
python3 scripts/meatloaf_debug.py type "hello"

# Decode IEC serial bus + user port ($DD00-$DD03)
python3 scripts/meatloaf_debug.py bus
python3 scripts/meatloaf_debug.py bus --samples 60 --interval 0.1  # is it moving?
python3 scripts/meatloaf_debug.py bus --srq      # also $DC0D.4 — DESTRUCTIVE

# STOP (interrupt running BASIC)
python3 scripts/meatloaf_debug.py stop

# Hardware reset
python3 scripts/meatloaf_debug.py reset

# Inject a compiled .prg (cc65, KickAss) — full cycle:
python3 scripts/meatloaf_debug.py reset
sleep 4
python3 -c "import requests
with open('path/to/foo.prg','rb') as f: body=f.read()[2:]
requests.post('http://192.168.1.176/v1/machine:writemem?address=0x0801', data=body, timeout=10)"
python3 scripts/meatloaf_debug.py type "RUN"
sleep 3
python3 scripts/meatloaf_debug.py screen

# Upload auxiliary data (sprite patterns, lookup tables, char sets)
python3 scripts/meatloaf_debug.py write-mem 5A00 sprites.bin
python3 scripts/meatloaf_debug.py write-mem 7000 sincos.bin

# Read back to verify
python3 scripts/meatloaf_debug.py read-mem 5A00 512
python3 scripts/meatloaf_debug.py read-mem 0801 19514 --raw > /tmp/prg_readback.bin

# Inject + RUN in one step
python3 scripts/meatloaf_debug.py test my_test.bas
python3 scripts/meatloaf_debug.py test --wait 5 < my_test.bas
Serial Capture
bash
# Start (auto-kills stale, auto-reconnects on port drops)
python3 scripts/meatloaf_debug.py capture start

# Stop
python3 scripts/meatloaf_debug.py capture stop

# Status + log size
python3 scripts/meatloaf_debug.py capture status

# Tail last N lines
python3 scripts/meatloaf_debug.py capture tail --lines 50

# Grep for diagnostics
python3 scripts/meatloaf_debug.py capture grep --pattern "Request URL"
python3 scripts/meatloaf_debug.py capture grep --pattern "perform OK"
python3 scripts/meatloaf_debug.py capture grep --pattern "sendRequest"

# Wait for data to appear (after flash)
python3 scripts/meatloaf_debug.py capture wait --timeout 20
Meatloaf Console (via serial FIFO)

The serial capture daemon holds the serial port open and provides a command FIFO at /tmp/serial_capture_cmd. Write a Meatloaf console command to the FIFO, and the daemon relays it over serial and captures the response in the log.

bash
# Send a command and read the response
python3 scripts/meatloaf_debug.py send "sysinfo"
python3 scripts/meatloaf_debug.py send "ifconfig" --wait 1.5
python3 scripts/meatloaf_debug.py send "meminfo"

Useful Meatloaf console commands:

CommandPurpose
sysinfoFirmware version, chip, flash/PSRAM info
meminfoHeap/PSRAM usage — check for memory leaks
ifconfigWiFi status, IP address
netstatActive TCP/UDP sockets — find stuck connections
ping <host>Network reachability (isolate C64 vs network)
iecdetectScan IEC bus for C64 device presence
config [key]Full or partial configuration JSON
dfFilesystem disk space
ls <path>List files on SD/flash
cat <file>View file content
psRunning tasks
rebootReboot Meatloaf (ESP32)
wget [-o <file>] <url>Download URL — test HTTP without the C64!
help [command]List or describe commands

Using wget for isolated HTTP tests: This is a powerful debugging technique. Instead of running the full C64→Meatloaf cycle, test HTTP directly from the Meatloaf console:

bash
# Download a URL to Meatloaf's temp filesystem
python3 scripts/meatloaf_debug.py send "wget -o /tmp/test.txt http://192.168.1.131:8080/echo" --wait 3

# Read the downloaded content
python3 scripts/meatloaf_debug.py send "cat /tmp/test.txt" --wait 2

Diagnostic logic: If wget works but C64 BASIC fails → the issue is in the BASIC code or IEC data path. If wget also fails → the issue is in Meatloaf's HTTP client or network layer.

Build & Flash
bash
# Show build settings resolved from platformio.ini (no hardware needed)
python3 scripts/meatloaf_debug.py config

# Build only
python3 scripts/meatloaf_debug.py build

# Build + flash — STOP THE CAPTURE FIRST or the upload fails (see above).
# A full rebuild after a framework/build-flag change takes several minutes;
# a source-only change is much quicker.
python3 scripts/meatloaf_debug.py capture stop
python3 scripts/meatloaf_debug.py build --flash
python3 scripts/meatloaf_debug.py capture start
Echo Test Server
bash
# Start on port 8080
python3 scripts/meatloaf_debug.py echo start

# Stop
python3 scripts/meatloaf_debug.py echo stop
Full Unattended Cycle
bash
# One cycle: ensure capture → inject → RUN → read screen
python3 scripts/meatloaf_debug.py cycle --basic my_test.bas

# With firmware flash first
python3 scripts/meatloaf_debug.py cycle --basic my_test.bas --firmware
Diagnostics
bash
python3 scripts/meatloaf_debug.py diag

Example output:

json
{
  "u64": "reachable",
  "screen_preview": "READY. | ... |",
  "u64_info": {
    "product": "Ultimate 64",
    "firmware": "3.12",
    "hostname": "ultimate-123456"
  },
  "serial_capture": {
    "running": true,
    "log_path": "/tmp/meatloaf_serial.log",
    "log_size": 18347
  },
  "echo_server": {
    "pid_file": false
  }
}

The Unattended Debug Cycle (Full Protocol)

Phase 1: Setup

Run once at the start of a session:

  1. config — confirm the environment, port and baud resolved from platformio.ini match the hardware actually plugged in
  2. diag — check U64 is reachable, serial capture status (its output includes config under build_config)
  3. capture start — ensure serial capture is running
  4. Start echo server if you'll do body verification: echo start
  5. reset or stop + clear — ensure the C64 is at READY
Phase 2: Identify what changed
ChangeActionWait time
BASIC code onlyinject + run "RUN"~2-3s
C code (cc65)reset → upload body → type "RUN"~8-10s
Meatloaf C++capture stop → build --flash → capture start → wait for serialminutes
Any combinationDo the slowest (flash) first, then C, then BASICN/A
Phase 3: Run and observe

For self-contained BASIC test programs:

python
screen = cycle.inject_and_run(basic_code, wait=wait_time)

For cc65 C programs (the correct deploy sequence):

bash
# 1. Reset (clears RAM, ensures clean memory)
python3 scripts/meatloaf_debug.py reset
sleep 4

# 2. Upload PRG body (strip the 2-byte LE load-address header)
U64_IP="${U64_IP_ADDRESS:-192.168.1.176}"
python3 -c "
import requests
with open('/path/to/foo.prg', 'rb') as f:
    body = f.read()[2:]
r = requests.post(f'http://${U64_IP}/v1/machine:writemem?address=0x0801',
                  data=body, timeout=10)
print(f'Upload {len(body)} B -> {r.status_code}')
"

# 3. Launch via RUN (the BASIC EXEHDR stub at $0801 handles SYS2061)
python3 scripts/meatloaf_debug.py type "RUN"
sleep 3
python3 scripts/meatloaf_debug.py screen

For interactive programs (menus, chat clients):

python
cycle.u64.type_text("What is the weather?")
time.sleep(5)  # Wait for HTTP round-trip
screen = cycle.u64.read_screen()
Show full SKILL.md (1,918 more words)Show less
Phase 4: Read two data sources (plus Meatloaf Console)

Meatloaf Console (via send command) — interact with the ESP32 directly. Use wget to test HTTP without the C64 involved. See the "Meatloaf Console" section above for full command reference.

Screen (via screen command) — what the C64 displays:

  • BASIC errors like ?SYNTAX ERROR IN 220
  • HTTP status: STATUS: 200
  • Error messages: ?DEVICE NOT PRESENT ERROR
  • Program output

Serial log (via capture tail or capture grep) — what Meatloaf logs:

Log patternMeaning
"Request URL:" or "HTTPMSession created"OPEN reached HTTP code ✓
handleCommandPRINT# commands being parsed
"GET url[" / "POST url["HTTP method dispatched
sendRequest: POST url=http://...POST body prepared
perform OK, status=###HTTP response received
BODY-CAPTURE: method=POST result=NResponse body captured
BUFFER: total=N (statusEnd=..., headersEnd=...)Response buffer built
Content-Length: 0Known bug: POST body not sent
opening stream failed, httpCode=###Server returned error
m_len =Buffer size tracking
Phase 5: Diagnose

Common patterns from evidence:

ScreenSerial logLikely issue
?DEVICE NOT PRESENTNothingMeatloaf not responding on IEC
Error with line #(anything)BASIC syntax bug
OK + status 200No perform OKStatus from cache (Q2 bug)
Status 200, no bodyperform OK with bodyBody capture order (read())
Status 0NothingURL open failed
Garbage textContent-Length: 0POST body not sent
Truncated textm_len highDuplicate m_len += got in drive.cpp
Works 1st call, hangs 2ndNo new perform OKQ2 stale state (unknown root cause)
Phase 6: Fix
  • BASIC issue → edit code, inject, run
  • Firmware issue → edit C++, build --flash, wait, inject, run
  • Loop until behavior matches expectations
Phase 7: Release the hardware

When the session ends — fixed, or handing back for someone to look at — release what the tooling is holding:

bash
python3 scripts/meatloaf_debug.py capture stop   # frees the serial port
python3 scripts/meatloaf_debug.py echo stop      # if the echo server was started

Remove any temporary instrumentation (timing probes, checkpoint writes to $D7FF) and reflash, so the device is left running the code the repo describes rather than a debug build.


Diagnostic Techniques

A. The Byte Dump Test (asc/dump)

When you suspect PETSCII/ASCII encoding issues in the IEC read path, inject a minimal program that prints raw byte values instead of characters:

basic
10 open 1,8,2,"http://192.168.1.131:8080/echo"
20 print#1,"m get"
30 print#1,"s"
40 print#1,"status"
50 get#1,a$:if st<>0 then 80
55 rem *** dump raw byte value ***
60 print asc(a$);" ";
70 goto 50
80 close 1

This tells you exactly what bytes the C64 receives — no encoding ambiguity. asc(a$) returns the raw byte from the server, NOT PETSCII-decoded.

B. Echo Server + Response Size Sweep

When response data is truncated, swap the real API for the echo server and test with varying body sizes:

bash
python3 scripts/meatloaf_debug.py echo start

Point BASIC at http://192.168.1.131:8080/echo.

The echo server logs every request with hex dumps and echoes back method, headers, and body — full round-trip visibility.

C. Single-Query vs Multi-Query Testing
  • Single-query (fresh inject per test) = proves firmware works for one request
  • Multi-query (shared BASIC session, multiple round-trips) = reveals state machine bugs

If single-query passes but multi-query fails → the bug is in session/state reuse.

D. Serial Recovery After Flash

Stop the capture before flashing (see "When to stop it"). Afterwards:

  1. Build + flash (the capture must not be holding the port)
  2. Wait 3s for ESP to reboot
  3. Wait up to 20s for serial data to reappear
  4. Proceed with inject + RUN

Manual recovery:

bash
python3 scripts/meatloaf_debug.py capture wait --timeout 20
E. Handle Stale BASIC Programs

If the C64 has a runaway program or one stuck in I/O:

bash
python3 scripts/meatloaf_debug.py stop
# Press ENTER for READY prompt
python3 scripts/meatloaf_debug.py type ""
# Now read screen to verify READY
python3 scripts/meatloaf_debug.py screen

If STOP doesn't work (program in KERNAL I/O), use reset:

bash
python3 scripts/meatloaf_debug.py reset
F. IEC bus and user port signals ($DD00-$DD03)

bus decodes CIA#2 in one read — the IEC serial lines, the user port byte used by parallel speeders, and both data direction registers.

bash
python3 scripts/meatloaf_debug.py bus

The polarity is not uniform, and this is the easiest thing to get wrong reading $DD00 by hand. OUT bits are inverted; IN bits are not:

BitLineSense
3ATN OUT1 = Low (asserted), 0 = High
4CLK OUT1 = Low (asserted), 0 = High
5DATA OUT1 = Low (asserted), 0 = High
6CLK IN1 = High, 0 = Low (asserted)
7DATA IN1 = High, 0 = Low (asserted)
0-1VIC-II bank (inverted)bank = 3 - (bits 0-1)
2RS-232 TXD / user port pin Malso a handshake line for parallel cables

Known-good idle baseline (C64 at READY, nothing on the bus) — compare against this first, since a stuck line shows up immediately:

$DD00 = $C7   all five IEC lines released
$DD02 = $3F   Port A DDR: bits 0-5 output, 6-7 input  (KERNAL default)
$DD01 = $FF   user port idle
$DD03 = $00   user port all inputs

SRQ is NOT in $DD00. On the C64 the IEC SRQ line arrives at CIA#1 /FLAG and is latched in $DC0D bit 4, shared with the datasette input. bus --srq samples it, but reading a 6526 ICR clears its latched interrupt flags — never do it during a live transfer or while anything depends on FLAG interrupts. That is why it is opt-in rather than part of the default read.

Parallel speeders (DolphinDOS / SpeedDOS) move the byte over the user port: $DD01 is the data, $DD03 the direction ($00 = all inputs, C64 receiving; $FF = all outputs, C64 sending). A parallel cable that is not working usually shows as $DD03 never leaving $00.

What it can and cannot see

Sampling goes over the U64 REST API at roughly 10 Hz, so it observes bus phases, not individual bit edges — IEC bit timing is microseconds.

That is more useful than it sounds. Measured on hardware 2026-08-19, sampling every 100 ms across a real LOAD"ML:RCS",8: 65 samples, zero timeouts, six distinct $DD00 values ($C7 idle, then $07 $27 $47 $67 $87 as CLK/DATA handshaked). So it answers the questions that actually matter:

  • Is a line stuck asserted? (compare against the idle baseline)
  • Is the bus doing anything at all? → bus --samples 60 --interval 0.1 reports bus active - N distinct states seen or bus idle or stuck
  • Is the DDR configured the way the protocol needs?

It will not give you a bit-level trace of a transfer. For that you need a logic analyser on the port, or Meatloaf-side VERBOSE_PROTOCOL logging.

Note the C64 stays responsive to readmem during an IEC transfer — the blocking-KERNAL-I/O caveat that affects keyboard injection does not stop memory reads.


Known Issues & Fix Patterns

1. POST Body Not Sent (Content-Length: 0)

Symptom: Server receives Content-Length: 0. Serial shows no sendRequest: POST or shows CL:0.

Root cause: openAndFetchHeaders() calls esp_http_client_open(_, 0) for POST/PUT before the body is set.

Fix in lib/network/http.cpp, function openAndFetchHeaders():

cpp
if (method == HTTP_METHOD_POST || method == HTTP_METHOD_PUT) {
    _is_open = true;
    return 200;  // provisional — real status from perform() in close()
}
2. Response Body Not Visible on C64

Symptom: STATUS: 200 printed but no body appears.

Root cause: read() checks _is_open before checking postResponse/preservedPostResponse. After close() destroys the HTTP handle, _is_open is false and the body pointers are read too late.

Fix in read(): check preservedPostResponse BEFORE _is_open, check postResponse BEFORE _is_open. Always transfer preservedPostResponse even when empty.

3. Buffer Truncation (Duplicated m_len += got)

Symptom: Response truncated at ~256 bytes body. Only with real API responses, not small echo server bodies.

Root cause: In lib/device/iec/drive.cpp:359-360, m_len += got; appears twice. This doubles the buffer-fill count. For responses >256 bytes, the doubled m_len exceeds BUFFER_SIZE=512 and the fill loop exits before eos() returns true.

Fix: Remove the duplicate m_len += got; (keeping the surrounding code). Confirmed fix, commit 51116209.

4. Q2: Second POST Uses Cached State

Symptom: First request works. Second request returns status 200 from stale state without making a new HTTP request. Serial shows no new perform OK.

Status: Root cause unknown. The sendRequest() → client.POST() path may return cached status without calling esp_http_client_perform().

Workaround: Close and reopen the channel between requests in BASIC:

basic
close ch
open ch,8,2,ap$

Or: re-inject the BASIC program fresh for each query (single-query pattern).


PETSCII Quick Guide

The C64 uses PETSCII, not ASCII. Here's what you actually need to know.

Screen reading (covered — just use the script)

The screen command reads $0400 screen memory and decodes PETSCII screen codes to ASCII. It always assumes mixed-case mode (the normal mode when Meatloaf or BASIC programs are running), which means:

  • Screen codes 1-26 → lowercase a-z
  • Screen codes 65-90 → uppercase A-Z
  • Everything else → punctuation, digits, or placeholder

No charset detection, no mode switching. Just works.

Known cosmetic quirk: What you see in the BASIC editor will have inverted letter case for direct keyboard-typed commands (e.g. RUN appears as run). This is normal PETSCII display behavior — BASIC parses the commands correctly either way. It does NOT affect the screen output from PRINT statements, which show the correct mixed case.

Keyboard buffer injection (send unshifted PETSCII — do NOT case-swap)

The keyboard buffer at $0277 expects PETSCII key codes:

Code rangeMeaningBASIC tokenises it?
0x41-0x5AUNSHIFTED letters — same byte values as ASCII A-Zyes
0x61-0x7ASHIFTED letters — drawn as graphics charactersno

So a BASIC command is injected as plain ASCII uppercase, byte for byte. type_text() passes those through unchanged and upcases ASCII lowercase into the same range, so type "PRINT 6*7" and type "print 6*7" both work.

There is no keyboard case inversion — do not reintroduce a swap. This skill used to swap the case of every letter before sending, which put every command in the 0x61-0x7A shifted range: the text appears on screen but BASIC answers ?SYNTAX ERROR, so it looks like a BASIC bug rather than an injection bug. Fixed and verified on hardware 2026-08-19 (PRINT 6*7 → 42).

The inversion is an artifact of reading the SCREEN back, not of the keyboard. _decode_screen() renders screen codes 1-26 as lowercase (it assumes mixed-case mode), so a command typed as uppercase reads back lowercase. That is the cosmetic quirk described above — it is not a signal to swap on input.

Shifted/graphics characters have no ASCII spelling; pass their PETSCII byte directly with chr() if you need one.

A. String literals in BASIC (tokenizer uppercases them)

When you write print#1,"b content", the C64 BASIC tokenizer converts alphabetic characters to uppercase in memory → "CONTENT".

Use chr$() to preserve case:

  • " → chr$(34) → define qu$=chr$(34) at program top
  • { → chr$(123) → define ob$=chr$(123)
  • } → chr$(125) → define cb$=chr$(125)
  • _ → chr$(95) → for command params like max_tokens
basic
10 qu$=chr$(34):ob$=chr$(123):cb$=chr$(125)
20 bd$=ob$+qu$+"name"+qu$+":"+qu$+"test"+qu$+cb$
B. Reading data via GET# (raw bytes from server)

asc(a$) returns the raw byte the server sent — no PETSCII conversion. If the server sends lowercase "content":, asc(a$)=99 matches chr$(99), NOT chr$(67) (uppercase C). This is reliable for byte-level comparisons.

C. Sending commands via PRINT# (firmware handles it)

The Meatloaf firmware calls toUTF8() on incoming command bytes to map PETSCII uppercase back to ASCII lowercase for case-insensitive matching. This is transparent to BASIC code.


Environment Variables

Every build setting defaults to what platformio.ini says. These variables exist to override it for a single run (a second board, a different port); config reports which ones are in force under overrides.

VariableDefaultPurpose
U64_IP_ADDRESS[meatloaf] u64_ip_address, else 192.168.1.176IP of the Ultimate 64
U64_PASSWORD(empty)X-Password header if configured. Env var only — never put it in platformio.ini, which is shared with the sample file
MEATLOAF_DIRauto-detected (nearest ancestor with a platformio.ini)Path to meatloaf firmware source
MEATLOAF_BUILD_ENV[meatloaf] environmentBuild environment (e.g. fujiloaf-rev0); also selects which [env:…] section board/flags are read from
MEATLOAF_UPLOAD_PORTmonitor_port / upload_portUpload and serial port
MEATLOAF_MONITOR_SPEEDmonitor_speedUART baud rate
MEATLOAF_DEBUG_SKILL_DIRthis skill's directoryLocation of scripts/

Trigger Keywords

"commodore 64", "c64", "ultimate 64", "u64", "meatloaf", "esp32", "iec", "petscii", "fujiloaf", "serial capture", "c64 remote", "c64 debugging", "commodore networking", "commodore HTTP", "1541u", "c64 basic", "$0801", "$0400", "keyboard buffer", "screen memory", "dev/ttyUSB0", "ultimate 64 api", "iec signals", "atn", "clk", "data", "srq", "$dd00", "$dd01", "$dd03", "$dc0d", "cia2", "cia1", "user port", "parallel cable", "dolphindos", "speeddos", "bus state", "stuck line", "data direction register", "cc65", "cl65", "ld65", "ca65", ".prg", "load address", "vice label", "vice monitor", "vice label file", "debugreg", "$d7ff", "main start", "exe header", "exe hdr", "kickass"


Cross-references

  • For writing Commodore 64 BASIC v2 programs (bc64 compiler, POKEs, sprites, SID, hardware control), see the c64-basic skill.
  • For the Meatloaf full-mode HTTP client protocol — the command language (m, h, b, s, status, j, c) and response reading patterns — see the meatloaf-networking skill.
  • For JSON Pointer (RFC 6901) syntax used with the j command, see json_pointer.md in the networking skill.
  • For JiffyDOS disk shortcuts, wedge commands, and fast disk I/O while debugging, see the c64-jiffydos skill.

© idolpx, GPL-3.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

SKILL.md and 4 other files (scripts) in .claude/skills/commodore64-debugging of idolpx/meatloaf.

  • SKILL.md
  • evals/evals.json
  • scripts/meatloaf_debug.py
  • scripts/pio_config.py
  • scripts/serial_capture.py

Open the folder on GitHubat commit 9a88a9f

Compare with similar skills

C64 Meatloaf Debug next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

C64 Meatloaf Debug compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
C64 Meatloaf Debug this skillidolpx/meatloaf128—~12kAutomated safety check: PassGPL-3.0
RuView CLI, API and WASMruvnet/RuView97k—~1.2kAutomated safety check: NotesMIT
Esp32 Firmware Engineeralxv2016/folloup-sticky1161 repos~3.8kAutomated safety check: PassGPL-3.0
Embedded DebugFastLED/FastLED7.5k—~1.4kAutomated safety check: PassMIT
Auto EmbeddedDunCanYounG-1/MICU-auto-embedded253—~1.6kAutomated safety check: PassCC-BY-NC-4.0
Embedded Cpp14 MisraBlueAndi/Pixelix443—~2kAutomated safety check: PassMIT

Similar skills

  • Covers the RuView `wifi-densepose` command line binary, its Axum REST API and the WebAssembly builds for browsers and ESP32, for embedding or scripting RuView.

    97k GitHub stars~1.2k tokensUpdated yesterday
    Backend & APIsAuto-check: notes
  • Esp32 Firmware Engineer

    alxv2016/folloup-sticky

    ESP32 firmware engineering for ESP-IDF projects. An agent skill from alxv2016/folloup-sticky.

    116 GitHub starsUsed in 1 repo~3.8k tokens
    DevelopmentAuto-check passed
  • Embedded Debug

    FastLED/FastLED

    Firmware crash analysis, stack trace decoder, and register dump interpreter for ESP32/ARM/AVR platforms.

    7.5k GitHub stars~1.4k tokensUpdated yesterday
    DevelopmentAuto-check passed
  • Auto Embedded

    DunCanYounG-1/MICU-auto-embedded

    全平台嵌入式 AI 开发框架(对标 Trellis):把 RIPER-5 五阶段协议 + 四文件记忆 + 分层架构门禁 + Scout/Builder/Verifier 多 Agent + 24 个工具调用技能(build/flash/debug/serial/can/modbus/visa/static/memory/rtos/scons),做成『装进工程、项目级 hook…

    253 GitHub stars~1.6k tokensUpdated 2 days ago
    DevelopmentAuto-check passed
  • Embedded Cpp14 Misra

    BlueAndi/Pixelix

    A skill your agent uses when writing, reviewing, or refactoring C/C++ firmware code in this repository (src/, lib/, test/) — creating or editing .h/.hpp/.cpp files, applying MISRA-oriented and…

    443 GitHub stars~2k tokensUpdated yesterday
    DevelopmentAuto-check passed
  • Embedded Cpp14 Misra

    BlueAndi/Pixelix

    Write, review and refactor embedded C/C++14 firmware code in this repository (src/, lib/, test/) with MISRA-oriented rules, defensive programming, Yoda conditions, single-exit/pathfinder control…

    443 GitHub stars~2.2k tokensUpdated yesterday
    DevelopmentAuto-check passed

More from idolpx/meatloaf

  • Debug Commodore 64 programs running under the VICE emulator through its binary monitor: BASIC, cc65-compiled C, and 6502 assembly.

    128 GitHub stars~4.8k tokensUpdated 3 days ago
    Auto-check passed
  • C64 Basic

    idolpx/meatloaf

    Write Commodore 64 BASIC v2 programs. An agent skill from idolpx/meatloaf.

    128 GitHub stars~2.8k tokensUpdated 3 days ago
    Auto-check passed
  • C64 Jiffydos

    idolpx/meatloaf

    Commodore 64/128 JiffyDOS reference — use whenever the user is working in a C64/128 emulator (VICE, Ultimate 64, etc.) and you see signs of the JiffyDOS replacement ROM: the boot saying "jiffydos…

    128 GitHub stars~4.5k tokensUpdated 3 days ago
    Auto-check passed
  • Meatloaf Dos

    idolpx/meatloaf

    Reference for Meatloaf's DOS command channel — the complete set of drive-level commands accepted over the IEC bus secondary address 15 on a Meatloaf ESP32 device.

    128 GitHub stars~7.4k tokensUpdated 3 days ago
    Auto-check passed
  • Meatloaf Networking

    idolpx/meatloaf

    Reference for the Meatloaf full-mode HTTP client protocol — the line-oriented command language (m, h, b, s, status, r-h, r-b, j, c) that an ESP32-based Meatloaf device accepts over the C64's IEC bus…

    128 GitHub stars~7.1k tokensUpdated 3 days ago
    Auto-check passed

Works with

Questions about C64 Meatloaf Debug

What does C64 Meatloaf Debug do?

Debug Commodore 64 BASIC programs, cc65-compiled C PRGs, and Meatloaf ESP32 firmware in tandem, using the Ultimate 64's REST API and Meatloaf's UART serial debug output. C64 Meatloaf Debug is an agent skill from idolpx/meatloaf. Debug Commodore 64 BASIC programs, cc65-compiled C PRGs, and Meatloaf ESP32 firmware in tandem, using the Ultimate 64's REST API and Meatloaf's UART serial debug output.

When should I use C64 Meatloaf Debug?

C64 Meatloaf Debug fits situations like: the user mentions debugging C64/Meatloaf interactions; meatloaf firmware issues; cc65 C networking; POST/PUT body problems.

How do I install C64 Meatloaf Debug in Claude Code?

Run `npx skills add idolpx/meatloaf --skill c64-meatloaf-debug -a claude-code`. Or copy the skill folder (.claude/skills/commodore64-debugging in idolpx/meatloaf) into .claude/skills/c64-meatloaf-debug in your project. Claude Code loads it when a task matches its description.

How do I install C64 Meatloaf Debug in Codex?

Run `npx skills add idolpx/meatloaf --skill c64-meatloaf-debug -a codex`. Or copy the skill folder (.claude/skills/commodore64-debugging in idolpx/meatloaf) into .agents/skills/c64-meatloaf-debug in your project. Codex loads it when a task matches its description.

Can I use C64 Meatloaf Debug in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add idolpx/meatloaf --skill c64-meatloaf-debug -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/c64-meatloaf-debug, .gemini/skills/c64-meatloaf-debug, .github/skills/c64-meatloaf-debug and .opencode/skills/c64-meatloaf-debug in your project.

What does C64 Meatloaf Debug need to run?

Going by SKILL.md and its folder, C64 Meatloaf Debug needs Python for the scripts in its folder, the command-line tools its instructions call (python3, pip, python, curl and wget) and credentials named U64_PASSWORD. Our summary lists: Python 3.

Does C64 Meatloaf Debug access the network?

SKILL.md names 1 domain. As links in the text: csdb.idolpx.com. This is read from the text; nothing was executed.

Is C64 Meatloaf Debug safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. The check reads SKILL.md only: the scripts in the folder are not scanned, so read them before running anything.

What licence does C64 Meatloaf Debug use?

C64 Meatloaf Debug is published under the GPL-3.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does C64 Meatloaf Debug use?

About 12k tokens (SKILL.md is roughly 47k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to C64 Meatloaf Debug?

Skills that share tags, products or a category with C64 Meatloaf Debug: RuView CLI, API and WASM (ruvnet/RuView, 97k stars), Esp32 Firmware Engineer (alxv2016/folloup-sticky, 116 stars), Embedded Debug (FastLED/FastLED, 7.5k stars) and Auto Embedded (DunCanYounG-1/MICU-auto-embedded, 253 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains C64 Meatloaf Debug?

idolpx (a GitHub user) maintains it in idolpx/meatloaf, which has 128 GitHub stars. The repository holds 6 skills in this directory. The repository was last updated on October 6, 2026.

Source: idolpx/meatloaf on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.