.kn86 Cartridge Format
The on-disk container format for cartridges. A .kn86 file is the unit the runtime mounts (per cartridge-lifecycle.md) and the unit the Fe compiler emits.
Authoritative decision: adr/ADR-0006 is the canonical decision record. Amendments through 2026-04-24 (post-ADR-0019) are reflected here. This doc is the implementer-facing extract of the byte-level format; the rationale and amendment history live in the ADR.
Related:
adr/ADR-0001— the Lisp/Fe authoring decision the format serves.adr/ADR-0004— the Fe VM the bytecode targets.adr/ADR-0019— the SD-card sled physical model; boundsstatic_data_sizeto the SD card capacity, not RAM.software/runtime/cartridge-lifecycle.md— when the format is parsed, validated, and unloaded.adr/ADR-0015§3a — the cart-capabilities flag system theCART_CAPABILITIESsubsection serves.
File Structure
Section titled “File Structure”┌─ .kn86 Cartridge Image (little-endian, all multi-byte values) ─┐│ │├─ HEADER (80 bytes, fixed) ││ Magic: "KN86" (4 bytes) ││ Version: 2 (uint16) + _reserved1 (uint16) ││ Cart ID: unique identifier (uint32) ││ Capability type: e.g., "NETWORK_INTRUSION" (32 bytes) ││ Declared req_api_version: e.g., 0x0201 (uint16) ││ Declared req_vm_version: e.g., 0x0100 (uint16) ││ Bytecode offset + size: (2 × uint32) ││ Static data offset + size: (2 × uint32) ││ Debug section offset + size: (2 × uint32) — 0s if none ││ Checksum: CRC-32 (uint32) ││ Reserved (4 bytes): padding for future use ││ │├─ BYTECODE SECTION (variable, aligned to 4-byte boundary) ││ Raw Fe-compiled bytecode blob ││ │├─ STATIC DATA SECTION (variable, aligned to 4-byte boundary) ││ Tagged sub-sections: SPRITES, PSG_PATTERNS, STRINGS, ││ MISSIONS, CART_CAPABILITIES, END ││ │├─ DEBUG SECTION (optional, aligned to 4-byte boundary) ││ Source, line tables, symbol names ││ │└─────────────────────────────────────────────────────────────────┘Header (80 bytes, little-endian)
Section titled “Header (80 bytes, little-endian)”struct CartridgeHeaderV2 { uint8_t magic[4]; /* "KN86" (0x4B 0x4E 0x38 0x36) */ uint16_t version; /* 2 (for v2.0) */ uint16_t _reserved1; /* Alignment padding */
uint32_t cart_id; /* CRC of (program_name + module_class) or author-assigned */
char capability_type[32]; /* e.g., "NETWORK_INTRUSION\0" */
uint16_t req_api_version; /* Min NoshAPI version (e.g., 0x0201 = v2.1) */ uint16_t req_vm_version; /* Min Fe VM version (e.g., 0x0100 = v1.0) */
uint32_t bytecode_offset; /* Byte offset to bytecode section */ uint32_t bytecode_size; /* Size in bytes */
uint32_t static_data_offset; /* Byte offset to static data */ uint32_t static_data_size; /* Size in bytes */
uint32_t debug_offset; /* Byte offset to debug section (0 if none) */ uint32_t debug_size; /* Size in bytes (0 if no debug) */
uint32_t checksum; /* CRC-32 of header + all sections (0 if not checked) */
uint32_t _reserved2; /* Future use */ /* Total: 80 bytes */};Field semantics
Section titled “Field semantics”magic— Always"KN86"(0x4B 0x4E 0x38 0x36). Identifies file type.version— Format version.2for this spec.cart_id— Unique cartridge identifier. Used for save-game isolation and mod tracking.capability_type— Domain string, null-terminated, max 31 chars (e.g.,"NETWORK_INTRUSION"). Primarily for UI.req_api_version— Semver-styleuint16(high byte = major, low byte = minor). Runtime rejects the cart ifnosh_api_version < req_api_version.req_vm_version— Min Fe VM version required.bytecode_offset/bytecode_size— Define the bytecode blob.static_data_offset/static_data_size— Define static data location. Bounded by the SD card capacity (gigabyte-scale per ADR-0019), not RAM. The runtime reads on demand from the cartridge’s mounted filesystem.debug_offset/debug_size— Optional debug section.debug_size == 0means absent.checksum— CRC-32 of[header..debug].0if not computed. Verification is optional (~100 µs cost).
Bytecode Section
Section titled “Bytecode Section”Opaque Fe-compiled bytecode blob.
The Fe compiler (desktop tool) takes .lsp source and outputs:
- Instruction stream (Fe opcodes, ~30–50 distinct).
- Constant table (literals: numbers, symbols, strings).
- Symbol table (optional, for debugging).
Pipeline:
source.lsp → Fe compiler → bytecode + constants → packager → .kn86Loading:
.kn86 → read header → validate version → load bytecode into arena → Fe evaluatorMVP note (per ADR-0006): v0.1 may ship .lsp source directly in the bytecode section (Fe reads source at load). A custom bytecode format is a Phase 2 optimization (~30% smaller carts). Both are compatible with this format.
Static Data Section
Section titled “Static Data Section”Organized as tagged sub-sections. Each carries a type tag, size, and payload:
[Type:SPRITES] [Size:4096] [Bitmap Data...][Type:PSG_PATTERNS] [Size:512] [Pattern Data...][Type:STRINGS] [Size:1024] [String Table...][Type:MISSIONS] [Size:2048] [Mission Template Data...][Type:CART_CAPABILITIES] [Size:26] [count + capability keywords][Type:END] [Size:0]Sub-section header (8 bytes)
Section titled “Sub-section header (8 bytes)”struct StaticDataSubsection { uint32_t type; /* Tag — see table below */ uint32_t size; /* Bytes of payload (excluding this header) */ /* Payload (size bytes) follows immediately */};Sub-section types
Section titled “Sub-section types”| Tag | Type | Payload |
|---|---|---|
0 | END | Empty (terminator). |
1 | SPRITES | 1-bpp row-major bitmap data. Sprite metadata (width, height, offset) lives in the section header. |
2 | PSG_PATTERNS | PSG register dump sequences: [pattern_id (2B), register_sequence...]. |
3 | STRINGS | String table: [string_id (2B), length (2B), null-terminated string...]. Cart code references strings by ID. |
4 | MISSIONS | Binary serialization of mission template structures (objectives, threat ranges, phase chains, payout formulas). Parsed at mission-board generation time. |
5 | CART_CAPABILITIES | Length-prefixed list of ASCII capability keywords the cart requests. Omit when no capabilities are requested (the v0.1 default for every launch cart except Null). See adr/ADR-0015 §3a. |
Forward compatibility: the runtime skips unknown type tags, so future sub-section types can be introduced without breaking older cartridges’ load path. Cartridges include only the sub-sections they need (no bloat from unused asset types).
Example: ICE Breaker (no capabilities)
Section titled “Example: ICE Breaker (no capabilities)”[Type:SPRITES] [Size:2048] [network_diagram, threat_meter, grid_bg, ...][Type:PSG_PATTERNS] [Size:256] [alarm_pattern, hack_complete_sting, ...][Type:STRINGS] [Size:512] ["CONTRACT_EXTRACT", "NETWORK_INTRUSION", ...][Type:MISSIONS] [Size:1024] [mission_meridian_extract, mission_cascade_vault, ...][Type:END] [Size:0]Example: Null (one capability flag)
Section titled “Example: Null (one capability flag)”[Type:SPRITES] [Size:1024] [cipher_debug_glyphs, ...][Type:STRINGS] [Size:512] ["CIPHER_ANALYSIS", ...][Type:MISSIONS] [Size:512] [mission_cipher_analysis, ...][Type:CART_CAPABILITIES] [Size:26] [count:1, _reserved:0, "cipher-main-grid-escape"][Type:END] [Size:0]Debug Section (optional)
Section titled “Debug Section (optional)”Present only when debug_size > 0. Omitted from production carts.
[Header] Type: "DEBUG_v1\0" (8 bytes) Line table size (uint32) Symbol table size (uint32) Source size (uint32)
[Line Table] Bytecode instruction offset → source line number Format: [instr_offset (uint32), line_number (uint32)]...
[Symbol Table] Symbols → internal indices Format: [symbol_hash (uint32), name_length (uint16), name (variable)]...
[Source Code] Original .lsp source (uncompressed text). May be truncated if oversize.Typical debug size: 10–20 KB for a 50-line cartridge. Strippable for production.
Checksum
Section titled “Checksum”CRC-32 over [header..debug]. Stored in the header’s checksum field. 0 means “not computed”. Verification is optional and costs ~100 µs on the runtime’s target hardware.
Validation order at load
Section titled “Validation order at load”The runtime validates in this order before registering the cart (see cartridge-lifecycle.md for the surrounding state machine):
magic == "KN86"→ else reject as not a cartridge.versionin supported range → else reject with version mismatch.nosh_api_version >= req_api_version→ else reject with API mismatch.vm_version >= req_vm_version→ else reject with VM mismatch.- Section offsets + sizes within file bounds → else reject as truncated.
- Optional CRC-32 check on
checksumfield → soft fail (warn, allow).
Any failure surfaces in fiction on the bare-deck terminal (> CARTRIDGE FORMAT NOT RECOGNIZED etc.) and the cart stays in MOUNTED state until removed.