ADR-0019: Cartridge Storage and Physical Form Factor (SD-Card Sled)
Partially supersedes: ADR-0017 — removes the DMG cartridge bus responsibility from the Pi Pico 2 coprocessor (ADR-0017 §“Decision item 2” cart-bus row, §“Decision item 3” cart-bus controller subsection, and the cart-related UART frame types in §“Decision item 4”). The Pico 2’s other two responsibilities — YM2149 PSG synthesis with I2S out to MAX98357A and the SSD1322 CIPHER-LINE OLED driver — are forced by audio-underrun and ticker-cadence considerations independent of the cart bus and remain in scope under ADR-0017. The Pico 2 stays in the canonical hardware; only the cart-bus role moves.
Related: ADR-0006 (open question #2 re-resolved here; closes via SD filesystem rather than via on-cart SRAM), ADR-0018 (USB hub topology — the SD card reader bridge IC lives on this hub), ADR-0017 (Pico 2 retains audio + OLED responsibilities; cart-bus role removed by this ADR), ADR-0011 (USB-MSC prior art on the Pi OTG path), CLAUDE.md (Canonical Hardware Specification — see hygiene rule 1)
Notion task: GWP-223
Context
Section titled “Context”ADR-0013 (Accepted 2026-04-22) committed the KN-86 to a retro-literal cartridge: Game Boy DMG 32-pin edge connector, MBC5 mapper, CR2032-backed on-cart SRAM for per-cartridge save state, and a 58 × 65 × 8 mm custom-molded shell dimensionally compatible with legacy DMG cartridges so the slot physically accepts them (the Phase-2 ghost_cartridge legacy-read easter-egg layer). The decision was earnest about the cartridge being hardware — silicon ROM, custom PCB, mapper IC, battery backup, precision edge connector — and a great deal of the rationale in ADR-0013’s Trade-off Analysis turned on what that hardware buys (capacity headroom, native per-cart save, legacy compatibility, sister-deck inheritance).
Stepping back two days later: KN-86 cartridges are not ROMs. They are bytecode files. Every authored cartridge is a .kn86 container (ADR-0006) of Fe VM bytecode plus tagged static data, executed inside an arena-allocated VM (ADR-0004) on the Pi Zero 2 W. The bytecode is indifferent to whether it was loaded from MBC5 ROM, a microSD partition, a USB mass-storage drive, or a TCP socket — it is bytes addressed by a loader. The cartridge’s job in the product, then, is ritual, collectibility, tactile identity, and homebrew-friendliness — not “be hardware.” Storage is a commodity implementation detail. Once that frame is taken seriously, ADR-0013’s expensive answers (MBC5 silicon, CR2032 cells, custom cartridge PCBs, edge connector tooling) are paying for benefits the bytecode does not need. This ADR replaces the storage and form-factor decisions with a commodity path that preserves the ritual. The fiction layer, the Lisp authoring story, the capability model, and the .kn86 format are unchanged — only the chunk of plastic the operator inserts and the bridge IC behind the slot are different.
Forcing functions
Section titled “Forcing functions”- ADR-0013’s industrial design and BOM commitments are imminent (90-day window from 2026-04-22). Recommitting the storage path before tooling money is spent is a 2-day ADR exercise; recommitting after is a 6-figure rework.
- ADR-0006’s open question #2 (per-cartridge save) was just closed by ADR-0013 via on-cart MBC5 SRAM (amendment 2026-04-22). Re-superseding within the week is awkward but cleanly bounded: the resolution moves from “battery-backed SRAM on cartridge” to “file on SD filesystem” without touching the
.kn86container or thecart_save/cart_loadFFI surface. - ADR-0018 (Accepted 2026-04-24) commits to an internal USB hub IC fanning the Pi OTG port out to the keyboard controller and a “cartridge bridge” downstream slot. The cartridge bridge in ADR-0018 was conceived as a CDC-class adapter speaking the MBC5 mapper protocol; substituting a USB-to-SD card reader bridge IC into that same downstream slot is mechanically and electrically free.
- The Pi Zero 2 W’s primary SDIO controller is consumed by the boot SD slot; the auxiliary SDIO is shared with onboard Wi-Fi and not practically free. SPI-to-SD is slow and driver-fiddly on Raspberry Pi OS. USB mass storage works with zero custom driver code —
udevsurfaces insertion events, the mounted filesystem is plainvfat/exfat, and the cartridge loader becomes aread()call.
Constraints
Section titled “Constraints”- Single hardware target: Raspberry Pi Zero 2 W. See
CLAUDE.mdCanonical Hardware Specification. - The internal USB hub topology in ADR-0018 has finite downstream ports. The card reader bridge IC must occupy the existing “cartridge bridge” slot; this ADR does not request a new USB port.
- Pelican 1170 interior depth budget is 80 mm (see
CLAUDE.mdCase row). Slot depth = shell depth + SD socket depth + PCB clearance. Verify against battery, speaker, and other components during bring-up. - The
.kn86container (ADR-0006), capability model (ADR-0001), Universal Deck State, cartridge history bitfield, and NoshAPI (ADR-0005) are out of scope. This ADR changes only the physical container around the bytecode and the bridge IC behind the slot. - Row 0 / Row 24 firmware-row layout, CIPHER-LINE Cipher-exclusivity, and primary-display semantics are untouched. Nothing in this ADR affects rendering.
Decision
Section titled “Decision”The KN-86 cartridge is a standard full-size SD card carried in a custom two-piece clamshell shell. The shell is the operator-facing artifact (ritual, collectibility, label surface, tactile mass); the SD card is the underlying storage. The card is read by the Pi Zero 2 W as USB mass storage through a card reader bridge IC on the internal USB hub planned in ADR-0018. Per-cartridge save data lives as a file on the SD filesystem.
- Storage medium. Full-size SD card. Full-size (not microSD) is chosen for tactile grip; the operator handles the shell, but a slipped-out card needs to feel like an artifact, not a sliver. Capacity floor TBD (see Open Questions §5).
- Interface to the Pi. USB mass storage via a card reader bridge IC. Candidate IC families include Genesys GL3224 / GL3232 and Realtek RTS5129; the specific part is selected during breadboard prototype (see Open Questions §1). The bridge IC occupies the downstream “cartridge bridge” slot already planned on the internal USB hub in ADR-0018; no new USB port is requested, and the keyboard controller’s downstream slot is unchanged. The Pi mounts the card as a standard block device (
/dev/sd*); the cartridge loader becomes aread()against the mounted filesystem. - SD socket. Full-size, push-push, surface-mount, with a card-detect switch surfaced for insertion/removal events. Candidate part families include Hirose DM1 series and Molex full-size SD push-push family; the specific part is selected during bring-up (see Open Questions §2). The socket mounts on a small internal PCB at the back of the slot.
- Cartridge form factor — sled architecture. The shell is a mechanical carrier around a standard full-size SD card. When the operator inserts the shell, the SD card’s contacts engage the push-push socket inside the device; retention is handled by the socket’s push-push mechanism, not by the shell. The user can open the shell to replace or re-flash the SD card (reloadable; supports homebrew).
- Shell dimensions. Retain the ~58 × 65 × 8 mm footprint from the superseded ADR-0013 (the Game Boy form-factor sweet spot — substantial enough to hold, label surface large enough for artwork, fits the existing industrial-design language). Two-piece clamshell, screw-closed. Wall thickness: 1.5–2.5 mm for production (ABS injection); 2–3 mm for prototypes (FDM PLA / PETG). Internal locating features (ribs, pockets) keep the SD card positioned during insertion; SD card lateral tolerance is ±0.3 mm.
- Per-cartridge save data. A file on the SD filesystem (e.g.,
/save/<cart_id>.sav). Supersedes ADR-0013’s battery-backed on-cart SRAM. Universal Deck State remains on the device, on the Pi’s microSD per ADR-0011 (unchanged). Save payload format continues to be ADR-0006 tagged static data (unchanged). Thecart_save/cart_loadNoshAPI contract is unchanged at the FFI surface; the implementation moves from MBC5 SRAM bank-switching to filesystem read/write underneath. Cartridge code does not observe the difference. - Hot-swap semantics. Physical insertion / removal generates
udevevents; the nOSh runtime subscribes. The capability-model Hot Swap (per the ICE BREAKER gameplay spec) now means: the operator physically ejects the cart, the nOSh runtime sees the mass-storage device disappear, cart state is preserved in the phase chain (Universal Deck State); the operator inserts a new cart, the nOSh runtime mounts it and loads the new module. The ~5-second Hot Swap timing in the ICE BREAKER spec still applies — it is now “how long the physical swap actually takes,” driven by socket push-push mechanics andudevenumeration latency rather than cartridge-firmware-managed state migration. - Device-side mechanical. A Pelican 1170 inset panel carries a cartridge slot opening sized for the shell (~60 × 10 mm). The SD socket mounts on an internal PCB at the back of the slot. Slot depth budget: shell depth (~65 mm) + SD socket depth + PCB clearance. The Pelican 1170 interior is 80 mm deep (
CLAUDE.mdCase row); verify against battery, speaker, amp, Pi mounting pocket, and CIPHER-LINE module during bring-up. Dust-door mechanism is deferred (see Open Questions §8).
Cartridge-present detect
Section titled “Cartridge-present detect”The card-detect switch on the SD socket is part of the mechanism for surfacing insertion/removal events to the nOSh runtime. The signaling path — whether the USB hub’s mass-storage device-disappear event alone is enough, or whether the card-detect switch needs a separate GPIO line into the Pi — is deferred to bring-up (Open Questions §9). Either path lands at the same nOSh-runtime-visible event (“cart present” / “cart absent”); the difference is latency and reliability under fast eject + reinsert.
What does NOT change
Section titled “What does NOT change”.kn86container (ADR-0006) — header, bytecode section, static data subsections,CART_CAPABILITIESblock, debug section, CRC-32. Unchanged.- Fe VM and bytecode (ADR-0004, ADR-0001). Unchanged.
- NoshAPI FFI surface (ADR-0005) — 54 primitives, three tiers.
cart_save/cart_loadkeep their signatures; only the storage backend changes underneath. Unchanged. - Capability model — mission board, phase chain, Universal Deck State, cartridge history bitfield, Cipher voice, hot-swap-as-designed-mechanic. Unchanged.
- Universal Deck State location — on the Pi’s microSD
/home/sharedpartition per ADR-0011. Unchanged. - Row 0 / Row 24 layout, CIPHER-LINE auxiliary display, primary display geometry, font cell, color palette. Unchanged. See
CLAUDE.mdCanonical Hardware Specification. - Sister-deck inheritance. Toneline, Gridline, Statline, etc. inherit the SD-sled cartridge format the same way they would have inherited DMG-pinout carts under ADR-0013.
- Pi Pico 2 coprocessor — audio (YM2149 + I2S → MAX98357A) and CIPHER-LINE OLED driver responsibilities (ADR-0017). The Pico 2 stays in the canonical hardware. Only the DMG cartridge bus responsibility, which assumed ADR-0013 as its forcing function, is removed by this ADR. The cart bus moves to a USB-mass-storage SD card via the bridge IC on the internal USB hub (per Decision §2 above) — the Pico 2 does not see the cartridge.
Consequences
Section titled “Consequences”Positive
Section titled “Positive”- Eliminates bespoke cartridge electronics. No MBC5 silicon, no custom cartridge PCB, no precision edge connector tooling, no CR2032 cells, no battery-backed SRAM ICs. The cartridge BOM is shell + SD card + label.
- Per-cart save is a file. No bank-switching protocol, no SRAM corruption window, no battery shelf-life.
cart_savewrites to the filesystem; the SD’s wear-leveling controller handles the underlying flash. - Homebrew is one screwdriver away. Open the shell, swap the SD, close the shell. No board-level rework, no ROM burner, no MBC5 chip to reprogram. The community ships
.kn86files; the operator drops them onto an SD with a desktop card reader. - Reuses commodity infrastructure. USB mass storage is the most-tested storage path in the Linux kernel. Zero custom driver code on the Pi side. The bridge IC families on the table are mature, in volume production, and well-supported.
- Fits cleanly into ADR-0018’s hub topology. The “cartridge bridge” downstream slot was already planned and budgeted; substituting a card reader bridge IC for the previously-imagined MBC5 CDC adapter is a part-substitution, not a topology change.
.kn86container size headroom is functionally unlimited. Where ADR-0013’s MBC5 ROM ceiling was 8 MB per cart (already luxurious against the 12–16 KB typical cart), the SD ceiling is the SD’s capacity (gigabytes). Cartridge content can grow without re-architecting the storage.- Sister decks inherit a cleaner shared standard. Toneline / Gridline / Statline get the same shell, the same socket family, the same bridge IC, the same filesystem layout. Cross-deck cartridge swaps “just work” at the physical layer.
Negative / Accepted costs
Section titled “Negative / Accepted costs”- The
ghost_cartridgePhase-2 subsystem (ADR-0013 §“Legacy-Cartridge Read Support”) cannot operate as designed. The slot no longer accepts legacy DMG cartridges — physically or electrically — so the ~20–30-title editorial easter-egg layer ADR-0013 promised does not exist on the SD-sled platform. Whether to retire it cleanly, reimagine it on the SD platform (e.g., SHA-1 of the entire SD image as the trigger; “alien cartridge” SD images traded by collectors), or build a separate accessory port for legacy cart reads is a deferred decision and is not answered by this ADR. Flagged as Open Questions §10 below. - The “Kinoshita licensed the DMG cartridge format from Nintendo in 1989” fiction beat from ADR-0013 §Action Items #5 is no longer load-bearing — the physical cartridge is not a DMG. The fiction is free to take a different beat (e.g., “Kinoshita standardized on a proprietary clamshell format for the consortium catalog”). Fiction update is downstream of this ADR; not in scope here.
- Tooling cost for the shell remains — two-piece clamshell injection-mold tooling at the same ~$15K–30K-per-color band ADR-0013 estimated for the DMG-shape shell. Lower than ADR-0013’s “shell + cartridge PCB + MBC5 + ROM + SRAM + battery” total, but not zero. The tooling is still a real budget line.
- The cartridge is more obviously “an SD card in a case” than the ADR-0013 design. A curious operator who opens the shell finds a commodity SD card. This is by design (homebrew-friendliness) and matches the ethos of the platform, but it does sand off some of the “this is bespoke silicon” texture that the DMG-shell pitch carried. Industrial design will need to compensate via shell finish, label artistry, and internal architecture (e.g., decorative SD card carrier or branded card wrap).
- Hot Swap timing depends on
udevand USB-MSC enumeration, both of which are well-tested but variable. Bring-up will need to characterize end-to-end “physical eject → nOSh runtime sees absence → physical insert → nOSh runtime sees presence → cart loaded” latency against the ICE BREAKER spec’s ~5-second budget.
Follow-on work this ADR creates
Section titled “Follow-on work this ADR creates”- CART-01 (Hardware): Breadboard the card reader bridge IC + SD push-push socket + USB hub harness. Deliverable: working USB-MSC enumeration through the ADR-0018 hub topology with a known-good SD card. Resolves Open Questions §1 and §2.
- CART-02 (Hardware): Industrial design pass on the two-piece clamshell shell. Confirm wall thickness, screw count and type, internal locating features against an SD card sample. Deliverable: STEP file + assembly drawing + first FDM print. Resolves Open Questions §3 and §6.
- CART-03 (Hardware): Pelican 1170 inset panel revision — slot cutout dimensions (~60 × 10 mm), internal SD-socket-PCB mounting pocket, dust-door provision (or open slot, pending Open Questions §8). Deliverable: panel CAD update + clearance check against existing component layout.
- CART-04 (Platform Engineering):
udevrule + nOSh runtime subscriber for SD-cart insertion / removal. Hot-swap event plumbing into the phase chain. Deliverable: working physical-swap event flow through to the existing capability-model Hot Swap entry point. Resolves Open Questions §9 if breadboard reveals USB-event-only is insufficient. - CART-05 (C Engineer): Migrate
cart_save/cart_loadimplementation from the MBC5-SRAM-shaped stub to the filesystem path. The FFI signatures stay; only the storage backend changes. Deliverable: implementation + tests + update to cart-load semantics in ADR-0006 §Loading Semantics step list (the existing “save cartridge-specific save data … to EEPROM” step becomes “save to SD filesystem path”). - CART-06 (Gameplay Design + PM): Decision on
ghost_cartridgedisposition (retire, reimagine, defer to accessory). Deliverable: a follow-up ADR or a documented retirement note. Owns Open Questions §10. - CART-07 (PM + Marketing): Decide whether the Launch-4 cartridges (ICE BREAKER, NeonGrid, Black Ledger, Depthcharge) ship as physical SD-sled carts or pre-installed on the device. Independent of this ADR; flagged as Open Questions §7.
Open Questions
Section titled “Open Questions”These are deferred to bring-up, future ADRs, or downstream design passes. None is answered in this ADR.
- Card reader bridge IC selection. Genesys GL3224, GL3232, Realtek RTS5129, or another candidate. Decided during breadboard prototype (CART-01).
- SD socket specific part number. Hirose DM1-xxxx, Molex SD-xxxx, or another full-size push-push family member with card-detect. Decided during breadboard prototype (CART-01).
- Shell closure hardware. Screw type (Phillips, tri-wing, hex), screw count (2 vs. 4), screw size (M1.6 vs. M2). Decided during industrial design pass (CART-02).
- Production shell material. ABS (period-accurate), PC/ABS (tougher), or PC (clarity option for special-edition variants). Decided during industrial design pass.
- Minimum SD capacity floor. The smallest supported card for a
.kn86cart (2 GB? 4 GB? 8 GB?). Driven by SD market availability and the smallest card the cart packager wants to provision. - Label / artwork system. Sticker, water-slide decal, UV print, or in-mold decoration. Decided during industrial design + first cartridge SKU pass.
- Launch-4 on-board vs. physical carts. RELATED BUT SEPARATE DECISION. A pending future ADR. If the Launch-4 (ICE BREAKER, NeonGrid, Black Ledger, Depthcharge) eventually ship pre-installed (not as physical carts), the physical cart format defined here still applies to Phase 2–4 modules. Not decided here.
- Dust-door mechanism. Spring-loaded flap, sliding cover, or open slot. Period-appropriate and aesthetically resolvable; defer to industrial design (CART-03).
- Cartridge-present detect signaling path. USB mass-storage device-disappear event alone vs. a separate GPIO line carrying the SD socket’s card-detect switch. Driven by measured
udevlatency and reliability under fast eject + reinsert sequences. ghost_cartridgedisposition. ADR-0013’s Phase-2 legacy-DMG-read easter-egg subsystem cannot operate as designed under SD-sled storage. Whether to retire cleanly, reimagine on the SD platform (alternate triggers — SHA-1 of full SD image, special “alien” SD images traded by collectors, etc.), or build a separate accessory port for legacy reads is a follow-up gameplay-design decision. CART-06.
Documentation Updates (REQUIRED — part of the decision, not aspirational)
Section titled “Documentation Updates (REQUIRED — part of the decision, not aspirational)”The PR landing this ADR ticks every box in this list, per CLAUDE.md Spec Hygiene Rule 3.
-
CLAUDE.md— Canonical Hardware Specification table: rewrite Cartridge pinout row, rewrite Cartridge shell row, remove the Cartridge mapper row (no longer applicable), rewrite Per-cartridge save row. -
docs/architecture/adr/ADR-0013-cartridge-physical-format.md— Status changed toSuperseded by ADR-0019. Supersession banner added at the top pointing to this ADR. Body retained as historical artifact. -
docs/architecture/adr/ADR-0017-realtime-io-coprocessor.md— Status updated to note partial supersession (cart-bus role removed; audio + OLED remain). Banner added at the top enumerating which sections of ADR-0017 are obsolete (cart-bus row in §“Decision item 2”, cart-bus controller in §“Decision item 3”, cart-related UART frame types in §“Decision item 4”) and which remain (PSG, OLED). Body retained as historical artifact for the obsoleted sections and as the source of truth for the still-in-scope responsibilities. -
docs/architecture/adr/ADR-0006-cart-format-v2.md— Amendment Log appended with a 2026-04-24 entry that re-resolves Known Unknown #2 (per-cartridge save) by pointing to ADR-0019. The 2026-04-22 amendment’s body is preserved as history; the new amendment supersedes its specific resolution mechanism (file on SD filesystem, not on-cart MBC5 SRAM). -
docs/architecture/adr/README.md— index row added for ADR-0019; ADR-0013 row marked Superseded. -
docs/hardware/KN-86-Pi-Zero-Build-Specification.md—Related:cross-references add ADR-0019 and update the ADR-0013 reference. Hardware Topology subsystem table: cartridge interface row added (or updated). BOM lines for DMG connector / MBC5 / CR2032 are removed; SD push-push socket + USB-to-SD card reader bridge IC lines are added. Mechanical section: cartridge slot dimensions and internal PCB location. -
docs/hardware/KN-86-Pi-Zero-Sourcing-Guide.md—Related:cross-references add ADR-0019. Remove DMG / MBC5 / CR2032 sourcing entries (none currently exist as concrete BOM lines under v0.1; cross-references in narrative text are updated). Add candidate-family entries for SD push-push socket (Hirose DM1, Molex full-size SD) and USB-to-SD card reader bridge IC (Genesys GL3224 / GL3232, Realtek RTS5129) with specific part numbers TBD. Internal-USB-hub line item (5c) updated to reflect the card reader bridge IC as the second downstream device. -
docs/KN-86-Definitive-Guide.md— Part 2 (Hardware) and Part 8 (Cartridge source anatomy /cart-savesemantics) cross-references checked; cartridge-storage references updated to point at ADR-0019. Part 16 (Decision log) row added for ADR-0019. -
docs/KN-86-Platform-Design-Master-Index.md— ADR table: ADR-0013 row marked Superseded; ADR-0019 row added. -
docs/architecture/KN-86-Capability-Model-Spec.md— Cartridge contribution model table row for “Per-cartridge save data” updated: “battery-backed SRAM on cart (ADR-0013)” → “file on SD filesystem (ADR-0019)”. -
docs/business/KN-86-PR-FAQ.md— Cartridge manufacturing economics paragraph (the “Custom DMG-compatible shells” reference) updated to reflect the SD-sled cartridge BOM shape. -
docs/build/compile.py— ADR build manifest: ADR-0019 entry added.
A PR that lands this ADR without ticking these boxes fails review. The grep-sweep report in the PR description enumerates every other reference to DMG / MBC5 / CR2032 / battery-backed SRAM / ghost_cartridge / ADR-0013 found in the tree and categorizes each as Fix in this PR, Flag only (historical artifact, archived doc, pre-existing stale reference outside this ADR’s scope), or Skip (unrelated subject — e.g., macOS .dmg disk images in CI scripts).
Narrative
Section titled “Narrative”ADR-0013 was an earnest commitment to the cartridge being hardware: silicon ROM, mapper IC, edge connector, battery-backed SRAM, custom shell. Two days later we noticed the bytecode does not care — it is bytes addressed by a loader, and every authored cartridge is a .kn86 file already. The expensive answers (MBC5, CR2032, custom cartridge PCBs) were paying for benefits a bytecode cart does not need. The cartridge’s real job is ritual, collectibility, label surface, tactile mass — the physical artifact the operator handles, branded and sized to feel substantial. So we keep the shell at GBA dimensions, keep the two-piece clamshell ergonomics, keep the slot ceremony, and put a commodity SD card inside a commodity USB card-reader bridge inside a commodity USB hub on a commodity Linux machine. The fiction layer, the Lisp authoring story, the capability model, and the .kn86 format are unchanged. Only the bridge IC behind the slot and the chunk of plastic the operator inserts are different, and they are different in the direction of “less bespoke silicon, more plastic-and-label” — which matches the ethos of a deck whose distinctive thing is that the fiction is load-bearing, not that the silicon is.