Authoring VPhone Patch Sets
Lakr233/vphone-cli
Explains how to declare a firmware patch in a vphone patch set, add a new set, or write a preset, including naming, gating and the checks that catch undeclared patches.
Walks through adding a new cartridge or peripheral IO handler to TeensyROM's MinimalBoot firmware and the three files that must stay in sync.
$ npx skills add SensoriumEmbedded/TeensyROM --skill add-io-handler -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install SensoriumEmbedded/TeensyROM add-io-handler --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/SensoriumEmbedded/TeensyROM.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.claude/skills/add-io-handler .claude/skills/add-io-handler && rm -rf skills-srcUse ~/.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/
Install the "add-io-handler" agent skill from https://github.com/SensoriumEmbedded/TeensyROM/tree/main/.claude/skills/add-io-handler into .claude/skills/add-io-handler/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "add-io-handler", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/SensoriumEmbedded/TeensyROM/tree/main/.claude/skills/add-io-handlerType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add SensoriumEmbedded/TeensyROM --skill add-io-handler -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install SensoriumEmbedded/TeensyROM add-io-handler --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/SensoriumEmbedded/TeensyROM.git skills-src && mkdir -p .agents/skills && cp -r skills-src/.claude/skills/add-io-handler .agents/skills/add-io-handler && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "add-io-handler" agent skill from https://github.com/SensoriumEmbedded/TeensyROM/tree/main/.claude/skills/add-io-handler into .agents/skills/add-io-handler/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "add-io-handler", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add SensoriumEmbedded/TeensyROM --skill add-io-handler -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install SensoriumEmbedded/TeensyROM add-io-handler --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/SensoriumEmbedded/TeensyROM.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/.claude/skills/add-io-handler .cursor/skills/add-io-handler && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "add-io-handler" agent skill from https://github.com/SensoriumEmbedded/TeensyROM/tree/main/.claude/skills/add-io-handler into .cursor/skills/add-io-handler/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "add-io-handler", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/SensoriumEmbedded/TeensyROM.git --path .claude/skills/add-io-handler--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add SensoriumEmbedded/TeensyROM --skill add-io-handler -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install SensoriumEmbedded/TeensyROM add-io-handler --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/SensoriumEmbedded/TeensyROM.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/.claude/skills/add-io-handler .gemini/skills/add-io-handler && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "add-io-handler" agent skill from https://github.com/SensoriumEmbedded/TeensyROM/tree/main/.claude/skills/add-io-handler into .gemini/skills/add-io-handler/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "add-io-handler", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install SensoriumEmbedded/TeensyROM add-io-handlerInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add SensoriumEmbedded/TeensyROM --skill add-io-handler -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/SensoriumEmbedded/TeensyROM.git skills-src && mkdir -p .github/skills && cp -r skills-src/.claude/skills/add-io-handler .github/skills/add-io-handler && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "add-io-handler" agent skill from https://github.com/SensoriumEmbedded/TeensyROM/tree/main/.claude/skills/add-io-handler into .github/skills/add-io-handler/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "add-io-handler", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add SensoriumEmbedded/TeensyROM --skill add-io-handler -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install SensoriumEmbedded/TeensyROM add-io-handler --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/SensoriumEmbedded/TeensyROM.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/.claude/skills/add-io-handler .opencode/skills/add-io-handler && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "add-io-handler" agent skill from https://github.com/SensoriumEmbedded/TeensyROM/tree/main/.claude/skills/add-io-handler into .opencode/skills/add-io-handler/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "add-io-handler", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
add-io-handlerWalks through adding a new cartridge or peripheral IO handler to TeensyROM's MinimalBoot firmware and the three files that must stay in sync.
A new piece of hardware plugs into MinimalBoot as a struct of function pointers. The handler file goes in a fixed IO_Handlers folder, best started from an existing handler of the same cartridge class, such as the two reference implementations for freezer-style carts with a control register and a physical freeze button, and any function slot that is not needed stays null rather than being stubbed out.
The handler then has to be registered at the same relative position in three files kept in sync by explicit source comments warning that the order must match: an enum entry, an include line plus an array entry, and, for a CRT-detectable cartridge, a row associating its type constant with the handler. Include order matters when a handler reads another handler's global, since all handler files are textually included into one translation unit. Two conventions found while integrating a real freezer cartridge round out the checklist: a reused global must be reset inside the new handler's own init function, and freeze completion can bypass side effects normal execution would otherwise trigger.
4 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit 5c1196f. It shows what the files ask for, not the result of running them.
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.
No scripts in the folder and no shell commands in SKILL.md (its code samples are c).
From the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
TeensyROM IO Handler Checklist loads about 1.8k tokens when it runs. Until then it costs about 162 tokens; SKILL.md has 807 words of instructions outside code blocks.
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.
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); files beside SKILL.md are not scanned.
The full file from SensoriumEmbedded/TeensyROM at commit 5c1196f, republished under its MIT licence (© SensoriumEmbedded). 807 words, ~1,820 tokens.
.claude/skills/add-io-handler/SKILL.md (or your agent's skills folder).A new piece of hardware (cartridge, freezer, peripheral) plugs into MinimalBoot as an
stcIOHandlers struct of function pointers. Most of the work is mechanical wiring across a
few files that must stay in sync; the rest is a handful of hardware-emulation conventions
that are easy to get subtly wrong because the compiler won't catch them.
Create Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_<Name>.c. Use an existing handler
of the same cartridge class as your template rather than starting from a blank page —
IOH_RetroReplay.c and IOH_SuperSnapshotV5.c are the reference implementations for
freezer-style carts with a control register and a physical freeze button.
Shape:
void InitHndlr_<Name>();
void IO1Hndlr_<Name>(uint8_t Address, bool R_Wn);
// ...other handler prototypes you need
stcIOHandlers IOHndlr_<Name> =
{
"<Name>", // Name of handler, IOHNameLength max
&InitHndlr_<Name>, // Called once at handler startup
&IO1Hndlr_<Name>, // IO1 R/W handler
&IO2Hndlr_<Name>, // IO2 R/W handler
NULL, // ROML Read handler (leave NULL if unused)
NULL, // ROMH Read handler (leave NULL if unused)
NULL, // Polled in main routine (leave NULL if unused)
&CycleHndlr_<Name>, // called at the end of EVERY c64 cycle (leave NULL if unused)
};Any slot you don't need stays NULL — don't stub out empty functions for them.
There are explicit "Synch order/qty" comments in the code warning that these must match — add your handler in the same position (relative to neighboring handlers) in all three:
Source/Teensy/MinimalBoot/Common/Menu_Regs.h — add IOH_<Name> to the
enumIOHandlers enum.Source/Teensy/MinimalBoot/Common/IOHandlers.h — add both the
#include "IO_Handlers/IOH_<Name>.c" line and the &IOHndlr_<Name> entry in the
IOHandler[] array, at the same position as the enum.Source/Teensy/MinimalBoot/Common/DriveDirLoad.h — if it's a CRT-detectable
cartridge type, add a (uint16_t)Cart_<Name>, IOH_<Name>, row to HWID_IOH_Assoc[]
(the Cart_<Name> constant is usually already #defined nearby from CRT-format docs).Include-order matters if your handler references another handler's global (see the
reused-global pattern below) — your #include must come after the file that declares that
global, since these are all textually included into one translation unit.
If the cartridge needs boot/CRT-type detection help (e.g. distinguishing which freezer cart
produced a given CRT file), that logic lives in Source/Teensy/FileParsers.ino's
SetTypeFromCRT(), typically behind the same feature-flag #ifdef as the handler
registration.
To save RAM, a handler may alias an existing global from another handler that's never
active at the same time, e.g. #define FC3_ControlReg RR_StatusReg, with a comment
explaining the reuse. This is a legitimate, established pattern — but MinimalBoot's
dispatcher (Source/Teensy/IOHandlers.ino and Source/Teensy/MinimalBoot/Min_DriveDirLoad.ino)
only calls the new handler's InitHndlr() when switching handlers. It never clears shared
globals in between, so whatever the previous handler left in that memory is still there
when yours starts.
If your init logic does something like ProcessControlReg(0); // HW power-on reset and
that function reads the persisted alias for anything (a status/hidden bit, a mode flag),
passing 0 as a function parameter doesn't touch the alias itself — you must explicitly
assign it too (FC3_ControlReg = 0;) before or as part of that call. Otherwise the first
thing your handler does on load is act on stale garbage from whatever ran before it.
Trace every read of a reused global back to where it's written, and make sure your Init
path covers all of them, not just the ones a fresh parameter conveniently overwrites.
Freezer cartridges share a button state machine: CycleHndlr_<Name>(bool R_Wn) runs every
C64 cycle, using shared globals CycleCountdown, CycCntFreeze, CycCntNumWr — a button
press sets CycleCountdown = CycCntFreeze, the handler waits for a read (asserting NMI),
then counts down CycCntNumWr consecutive writes before completing the transition into the
frozen state.
At that completion point, route the transition through your own control-register-processing
function with a synthetic value representing the end state — e.g. RetroReplay does
ProcessRRControlReg(RR_CR_nGAME | RR_CR_EXROM) — rather than hand-setting individual pins
(SetNMIDeassert; SetGameAssert;). The control-register function is where all the side
effects of a mode change live (bank switching, ROM pointer updates, and — critically for
Ultimax-mode freezers — toggling EmulateVicCycles/disabling IRQ_ENET/IRQ_PIT, which
prevents graphics tearing in the freezer menu). Hand-setting a couple of pins reproduces the
visible pin state but silently skips everything else that function does, which is exactly
the kind of bug that only shows up as a glitch at the exact moment of freezing.
//rtBin8kHi or None /
//rtBin16k or 8kLo — reuse this shorthand so the four combinations stay easy to read
across handlers.BankNum is set from control-register bits and indexes
CrtChips[BankNum].ChipROM. The bit-mask width must exactly match how many banks the
ROM size implies for your cartridge's variants (e.g. a 4-chip 64K variant needs & 3,
a 16-chip 256K variant needs & 15) — derive this from the actual ROM size math for your
hardware, don't copy a mask from a handler with a different bank count.Re-read your InitHndlr_<Name>() against every global it reads (including reused/aliased
ones) and confirm none of them can hold a stale value from whatever handler ran previously.
Re-read your CycleHndlr_<Name>()'s freeze-completion branch and confirm it transitions
state through the same function a real register write would use, not a hand-picked subset
of pins.
© SensoriumEmbedded, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in .claude/skills/add-io-handler of SensoriumEmbedded/TeensyROM.
Open the folder on GitHubat commit 5c1196f
TeensyROM IO Handler Checklist 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.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| TeensyROM IO Handler Checklist this skillSensoriumEmbedded/TeensyROM | 227 | — | ~1.8k | Automated safety check: Pass | MIT | |
| Authoring VPhone Patch SetsLakr233/vphone-cli | 15k | — | ~4.6k | Automated safety check: Pass | MIT | |
| Sipeed I2C and SPI Hardware Controlsipeed/picoclaw | 30k | — | ~578 | Automated safety check: Pass | MIT | |
| RuView Hardware Setupruvnet/RuView | 97k | — | ~1.8k | Automated safety check: Notes | MIT | |
| Esp32 Firmware Engineeralxv2016/folloup-sticky | 115 | 1 repos | ~3.8k | Automated safety check: Pass | GPL-3.0 | |
| ExecuTorch Binary Size Reductionpytorch/executorch | 5.1k | — | ~793 | Automated safety check: Pass | Custom licence |
Lakr233/vphone-cli
Explains how to declare a firmware patch in a vphone patch set, add a new set, or write a preset, including naming, gating and the checks that catch undeclared patches.
sipeed/picoclaw
Reads and controls I2C and SPI peripherals on Sipeed boards such as LicheeRV Nano, MaixCAM and NanoKVM through the i2c and spi tools.
ruvnet/RuView
Brings a RuView CSI sensing node online by building ESP32-S3 or ESP32-C6 firmware, flashing the board, provisioning WiFi and checking the serial output.
alxv2016/folloup-sticky
ESP32 firmware engineering for ESP-IDF projects. An agent skill from alxv2016/folloup-sticky.
pytorch/executorch
Measures and shrinks the ExecuTorch runtime binary by building a size test, analyzing it with bloaty and landing each reduction as its own pull request.
ruvnet/RuView
Sets up and runs 60 GHz and 24 GHz mmWave radar sensing on ESP32 boards in RuView, alone or fused with WiFi CSI.
SensoriumEmbedded/TeensyROM
Adds a Commodore 64 program, cartridge, SID or picture to TeensyROM's built-in menu by converting it to a PROGMEM header and wiring it into MainMenuItems.h.
SensoriumEmbedded/TeensyROM
Opens Beyond Compare on a git diff for the current work, a pull request, a branch, a commit or a range, without touching the checkout.
SensoriumEmbedded/TeensyROM
Explains how to build the C64 menu programs and the dual-boot TeensyROM firmware from the repo and flash it to one or more boards over USB.
Categories
Walks through adding a new cartridge or peripheral IO handler to TeensyROM's MinimalBoot firmware and the three files that must stay in sync. A new piece of hardware plugs into MinimalBoot as a struct of function pointers. The handler file goes in a fixed IO_Handlers folder, best started from an existing handler of the same cartridge class, such as the two reference implementations for freezer-style carts with a control register and a physical freeze button, and any function slot that is not needed stays null rather than being stubbed out.
TeensyROM IO Handler Checklist fits situations like: adding a new cartridge or peripheral IO handler to TeensyROM; reviewing a contributed handler before it is registered; checking that a handler is wired into all three required files; debugging a freezer-style cartridge handler's init or freeze behavior.
Run `npx skills add SensoriumEmbedded/TeensyROM --skill add-io-handler -a claude-code`. Or copy the skill folder (.claude/skills/add-io-handler in SensoriumEmbedded/TeensyROM) into .claude/skills/add-io-handler in your project. Claude Code loads it when a task matches its description.
Run `npx skills add SensoriumEmbedded/TeensyROM --skill add-io-handler -a codex`. Or copy the skill folder (.claude/skills/add-io-handler in SensoriumEmbedded/TeensyROM) into .agents/skills/add-io-handler in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add SensoriumEmbedded/TeensyROM --skill add-io-handler -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/add-io-handler, .gemini/skills/add-io-handler, .github/skills/add-io-handler and .opencode/skills/add-io-handler in your project.
SKILL.md names no scripts, command-line tools or credentials: TeensyROM IO Handler Checklist is instructions for the agent only. Our summary lists: The TeensyROM MinimalBoot firmware source tree.
SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.
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. Review the folder before installing.
TeensyROM IO Handler Checklist is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 1.8k tokens (SKILL.md is roughly 7.3k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with TeensyROM IO Handler Checklist: Authoring VPhone Patch Sets (Lakr233/vphone-cli, 15k stars), Sipeed I2C and SPI Hardware Control (sipeed/picoclaw, 30k stars), RuView Hardware Setup (ruvnet/RuView, 97k stars) and Esp32 Firmware Engineer (alxv2016/folloup-sticky, 115 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
SensoriumEmbedded (a GitHub user) maintains it in SensoriumEmbedded/TeensyROM, which has 227 GitHub stars. The repository holds 4 skills in this directory. The repository was last updated on October 5, 2026.
Source: SensoriumEmbedded/TeensyROM on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.