Skip to content

Latest commit

 

History

5 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Cartwright

A cartwright builds carts.

A Claude Code skill for building the SD card of a modern handheld retro console, with an agent doing the work.

A TrimUI Brick Pro is a Game Boy that also runs a Mega Drive, a PlayStation, a PSP and an arcade cabinet, off one memory card. Getting one into that state is two or three days of archaeology. Which BIOS filename does this core actually want. Why do all 375 arcade games refuse to start. Where does box art go, and what do you do about the games the scraper cannot find. How do you prove that a 240 GB copy arrived intact rather than finding out in a month.

That archaeology is done. This skill is the result, written for an agent to execute rather than for you to read.

Two ways in

Something is broken. Say what you see and let Claude work backwards.

my arcade games all say "missing files for THIS VERSION of FBNeo"

half my Saturn games have no box art

every PlayStation game shows up twice

The skill opens on a symptom table that routes each of these to a first move, usually a diagnostic rather than a fix. There is a reason for that: in this hobby the obvious repair is normally wrong, and the device already wrote the real reason into a log.

You have an empty card and a drive full of ROMs. Then it is the whole pipeline, twelve phases, in order:

build an SD card for my Brick Pro from this drive

Claude finds the platform tag, interrogates each emulator core, pulls the BIOS files those cores name (a few hundred KB out of multi-GB packs), sorts the ROMs, rebuilds the arcade sets against the core's own CRCs, formats the card, copies, then verifies every file against a manifest before saying it is done. The per-system work is independent, so scraping, rebuilding and verifying fan out across subagents cleanly if you want the whole thing at once.

Done end to end once, on real hardware, that produced:

Systems       19
Games         2,718
Box art       2,716 of 2,716
Collections   71 (10 by genre, 61 by franchise), 2,149 entries, 0 broken
BIOS files    391 (149 MB)
Arcade        375 romsets, none running  ->  541 working
Corrupted     0

Every path, command and number in this repo came off that hardware rather than out of a wiki.

Install

git clone https://github.com/chiotas/cartwright ~/.claude/skills/cartwright

Claude loads it when it recognises the domain. Nothing else to configure.

The one rule

Never trust a database, a filename, a wiki, or this skill. Interrogate the binary.

Half the work in the original build went into discovering that documentation, DAT files and ROM sets all disagreed with what the emulator on the device wanted. Ask the core directly and the problem dissolves. Assume, and you lose an hour.

corespec.py ext picodrive_libretro.so
# bin|gen|smd|md|32x|cue|iso|chd|sms|gg|sg|sc|m3u|68k|sgd|pco

The arcade fix, end to end

The flagship case, in full. Every command here was run against a real FBNeo core and a real DAT.

B=https://raw.githubusercontent.com/libretro/FBNeo/master/dats
curl -sLo arcade.dat "$B/FinalBurn%20Neo%20(ClrMame%20Pro%20XML,%20Arcade%20only).dat"

# the core that will actually run, straight off the device
ssh root@$IP 'cat /mnt/SDCARD/.system/tg5040/cores/fbneo_libretro.so' > fbneo.so

# does this dat describe that core? do not skip this
fbneo.py validate --dat arcade.dat --core fbneo.so
# 300/300 sampled rom names present in fbneo.so  (100%)
# MATCH — this dat describes this core

fbneo.py rebuild --dat arcade.dat --roms "Roms/Arcade (FBN)"           # dry run
fbneo.py rebuild --dat arcade.dat --roms "Roms/Arcade (FBN)" --apply
cat "Roms/.rebuilt/merge.sh"     # it deletes files, so read it
sh   "Roms/.rebuilt/merge.sh"

fbneo.py maptxt --dat arcade.dat --roms "Roms/Arcade (FBN)"

What it covers

Twelve phases, each with a reference file the agent reads on demand:

  1. Recon: platform tag, folder tags, core extensions, core BIOS names
  2. Installing the OS into staging
  3. BIOS, validated against the cores rather than dumped from a pack
  4. Sorting ROMs, unzipping, multi-disc .m3u handling
  5. The arcade problem: rebuilding MAME sets into FBNeo sets by CRC
  6. Formatting, copying, and proving the copy is intact
  7. SSH, paks, reading the device's own logs
  8. Box art, including the games the scraper misses
  9. Cross-system collections by genre and franchise
  10. Theming, and rebuilding what a theme leaves out
  11. Backups over rclone to a NAS
  12. Round trip and re-verification

gotchas.md collects the rest. It is short and boring and it will save you an afternoon.

The five tools

Every script runs standalone, needs nothing but Python 3, and has a --selftest.

ziprange.py reads a remote zip over HTTP range requests without downloading it. It creates a sparse local file, writes only the tail into it, and hands that to zipfile, which walks the full central directory and never touches the hole. 391 BIOS files out of multi-GB packs. 496 arcade romsets out of a 9.63 GB archive, for 3.5 GB of traffic. Split .zip.001 archives work too.

corespec.py interrogates a libretro core: accepted extensions (taking the longest pipe-separated string, which is the trap), expected BIOS filenames, whether the core embeds its ROMs, and whether an FBNeo core uses pre- or post-rename romset names.

fbneo.py is the reason this repo exists. It parses an FBNeo DAT properly, skipping merge= entries and CRC-less nodump entries, both of which break naive parsers. validate then proves the DAT describes the core you are about to run, by sampling ROM filenames and probing the binary for them, so you never have to reason about version tags. After that it indexes your zips by CRC, works out which romset each one contains, and rebuilds it under the filenames the core wants, renaming the zip when the content turns out to be a different set. 1942 was really 1942a. gng was really gngb. It writes to a separate folder, never touches your originals, and emits a merge.sh you read before running, because the rebuilt folder holds only what changed and swapping it in wholesale would delete every set that already worked. It also generates map.txt with real titles and the arcade BIOS hidden from the menu.

manifest.py fingerprints a source tree by size and CRC32, then proves the copy is intact. A 240 GB card that drops one file without saying so is the worst outcome in this hobby: you find out weeks later, when a game will not boot. It also detects sparse files, which report full size in ls and pass unzip -l while containing nothing.

libretro.py pulls box art from libretro-thumbnails and genre and franchise metadata from libretro-database. Both are public, no account, no rate limit worth worrying about. It matches by region, so a USA cart does not end up wearing a Japanese box.

Device scope

Verified on a TrimUI Brick Pro (tg5040) running NextUI.

The folder conventions, map.txt, the .media artwork layout, the Collections/ format and the pak system are shared across the MinUI family: TrimUI Brick, Smart Pro, and MinUI on the RG35XX line. Expect those to hold and the platform tag to differ.

On anything else (muOS, Knulli, ArkOS, Batocera, ES-DE, desktop RetroArch) the method still holds and all five tools still run, because CRCs, libretro cores, exFAT and HTTP do not care which frontend you use. Only the destination paths change. references/porting.md lists which parts transfer, and for each device-specific convention it gives you a way to derive it from the hardware in front of you. Read it before adapting anything, or you will invent paths that fail silently.

ROMs

Say what you want and Claude goes looking: it picks the right convention first (FBNeo for arcade, No-Intro for cartridges, Redump for discs), queries the archive.org API, checks each candidate for access restrictions and actual contents, and comes back with options and sizes for you to choose from. Then it pulls only the entries you need and verifies them before building on them.

What it will not do is carry a list of ROM sites. Those rot within months, and a live search beats a stale list every time. Your own dumps and your own drives work the same way.

Where it came from

One SD card, built from scratch over a couple of days and documented while it happened. The original field guide is still worth reading if you want the narrative rather than the tooling. This repo is that guide turned into something an agent can execute.

The Dreamcast section exists because a wrong answer cost someone 38 GB of deleted ROMs.

License

MIT.

About

Build the SD card of a modern retro handheld with Claude Code. TrimUI Brick Pro / NextUI verified, portable to any libretro handheld.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages