Skip to content

ADR-0015: CIPHER-LINE Auxiliary OLED Display

The Cipher voice has been a nOSh-runtime-owned capability since the standalone-cartridge model was retired. Until now it has rendered on the primary 80×25 amber monochrome grid — either as a typewriter block inside a Detail View, as a ticker on Row 21 of the Bare Deck’s HOME box, or as a one- or two-line debrief after a phase completes. In every case it competes with cartridge content for the same pixel real estate, and for several adjacent surfaces (battery meter, gameplay timer, operator-assigned seed scratch) the nOSh runtime has had to either burn one of the 23 content rows or live with the surface being absent from the display entirely.

Three forcing functions accumulated:

  1. Cipher crowds cartridges. Every module that has been through a Round 2 review has a screen where the Cipher ticker, phase commentary, or debrief passage steals rows the module otherwise needed. The current layout asks cartridges to either cede a row to nOSh runtime narration or ask the nOSh runtime to be silent while they work — both outcomes are bad.
  2. Utility surfaces have no home. Battery state, an operator-visible mission timer, context-sensitive TERM hints, seed display for procedural capture, and a contextual “current meta” row (mission phase, operator reputation delta, linked-deck handshake status) have nowhere to live. They have been drifting through Row 0 (firmware status bar), Row 24 (firmware action bar), or — worst — being inlined into cartridge text as strings.
  3. Cipher-on-main-grid was never the authoring boundary we wanted. The capability-model fiction frames Cipher as “the deck has a voice” — a system-level colleague, not a cartridge character. Having it render on the same surface as cartridge output conflates the two.

Josh approved a new auxiliary OLED display on 2026-04-24 to absorb all three problems. This ADR ratifies the hardware choice, the surface allocation, the authoring boundary between the nOSh runtime and cartridges, and the FFI primitives that make the new surface usable from cartridge Lisp.

  • The device already has a locked canonical hardware spec (processor, main display, case, battery, audio chain — see CLAUDE.md Canonical Hardware Specification). The auxiliary display must slot in without disturbing any of those values.
  • The 3000 mAh LiPo pack and ~20-hour runtime target are non-negotiable. Any auxiliary-display power draw must stay inside that envelope or shift the runtime expectation by a measurable and documented amount.
  • The Pelican 1170 shell is not machined or modified (per the case row in CLAUDE.md). The auxiliary display mounts via a 3D-printed inset panel; the shell itself stays off-the-shelf.
  • Cartridge authoring must stay uniform. Every cart author gets the same FFI, whether they are writing for the main grid or the auxiliary display; no cart has special access to nOSh runtime internals.
  • Spec Hygiene Rule 1 forbids restating canonical numeric values in new docs. This ADR cites CLAUDE.md for every value that row table already carries and only documents values this ADR introduces.

The KN-86 adopts a dedicated auxiliary OLED display — the CIPHER-LINE — mounted above the key plate inside the Pelican 1170 inset panel. CIPHER-LINE is the exclusive home for the Cipher voice and absorbs a fixed set of utility surfaces that do not belong on the primary 80×25 grid. Canonical hardware values (module family, pixel dimensions, pixel colour) live in the new Auxiliary Display row of CLAUDE.md’s Canonical Hardware Specification; this ADR references them rather than restating them.

Concrete commitments:

  • Module class: 3.12” OLED, SSD1322 controller, 256×64 pixels, yellow pixel element. Yellow is close enough to the amber #E6A020 of the main display that, with the monochrome constraint preserved, the two surfaces read as the same colour family to the operator. See CLAUDE.md Canonical Hardware Specification (Auxiliary Display row) for the authoritative values.
  • Interface: 4-wire SPI driven by the Pi Pico 2 coprocessor; commanded by the Pi over UART per ADR-0017 (amendment landed 2026-04-24). The pin assignment below is the pre-ADR-0017 Pi-side proposal, retained for design-history context — it is superseded; the actual SPI host is the Pico 2 and the final pin assignment is finalised at F2 firmware bring-up (kn86-pico/). The cart-facing FFI surface (cipher-emit, aux-*) is unchanged. Proposed pin assignment (PRE-ADR-0017, no longer applies):
    • MOSI: GPIO10 (pin 19) — SPI0 MOSI, standard location.
    • SCLK: GPIO11 (pin 23) — SPI0 SCLK, standard location.
    • CE0 (chip select): GPIO8 (pin 24) — SPI0 CE0, standard location.
    • DC (data/command): GPIO25 (pin 22) — free, adjacent to CE0, short trace run. TBD during bring-up if a conflict arises with an existing peripheral we’ve not inventoried.
    • RST (reset): GPIO24 (pin 18) — free, same GPIO bank. TBD during bring-up for the same reason.
    • VCC / GND: 3.3 V rail + GND from the Pi header.
  • Why SPI0 and not SPI1/bit-bang: SPI0 is hardware-accelerated on the BCM2837 SoC (~20 MHz achievable clock), is the standard SPI bus with robust Linux kernel driver support (spi_bcm2835), and leaves SPI1 available for a future peripheral. Bit-banging burns CPU against our event-driven render budget.
  • Mount: above the key plate, centered on the operator’s sight line. The bezel cutout is a window in the Pelican 1170 inset panel — the printed panel carries the cutout and a foam gasket; the Pelican shell is not modified. Exact cutout dimensions are TBD during mechanical bring-up once we have a physical module in hand to measure against the panel CAD.
  • Power: the module draws ~10–30 mA typical / ~40 mA peak from the 3.3 V rail. Against the 120–180 mA typical draw cited in CLAUDE.md, that is an 8–17% increment on the typical band and a 22% increment on the peak band. The runtime figure in CLAUDE.md (~20 hours from a 3000 mAh pack) remains valid as a ballpark — the increment pushes the low end of the typical band toward ~19 hours. We accept that small shift; if bring-up measurement shows real draw above the 40 mA peak estimate, we revisit the CLAUDE.md Battery row.

CIPHER-LINE uses Press Start 2P at its native 8×8 source bitmap, rendered 1:1 on the 256×64 panel — 32 character cells wide × 8 rows tall at a strict 8×8 cell. We carve that canvas into a four-row logical layout of ~32 characters each. Each logical row is 16 panel-pixel rows tall (2 × 8 source px), giving the operator a readable line height from normal sight distance without requiring a font re-cut.

Logical rowPurposeOwner
Row 1Status strip: battery glyph, mission/gameplay timer, mode indicator (e.g., DEV / PROD / LINK), TERM-hint chip.nOSh runtime
Row 2CIPHER scrollback — current fragment (the live utterance).nOSh runtime generator; cartridge vocabulary contributes.
Row 3CIPHER scrollback — previous echo (the prior utterance, fading).nOSh runtime generator; cartridge vocabulary contributes.
Row 4Contextual surface: seed capture (when seed-display mode is active) / gameplay sub-timer / mission phase meta / linked-deck status.nOSh runtime (mode-switched by cart / phase).

Cartridges do not address CIPHER-LINE rows by coordinate. They call the FFI primitives in §4 and the nOSh runtime places text in the right row.

The Cipher voice renders exclusively on CIPHER-LINE. No CIPHER glyph is drawn on the primary 80×25 grid after this ADR lands. This is a hard architectural boundary, not a stylistic preference. It shows up in two places:

  • The nOSh runtime removes main-grid Cipher surfaces. The Row 21 Cipher ticker on the Bare Deck HOME box, the Cipher-voice announcement blocks that previously landed in Detail View, and the debrief passage block that landed on phase completion are all retired from the main grid and relocated to CIPHER-LINE. The UI Design System and Bare Deck Terminal Spec updates are in scope for the Gameplay Design agent’s follow-up, not this ADR.
  • Cartridges cannot render Cipher glyphs on the main grid. There is no API for a cart to write a Cipher passage into a text-puts call. The cipher-* primitives target CIPHER-LINE only; text-* primitives target the main grid only. The nOSh runtime enforces this at the FFI boundary — a cart that asks Cipher to talk can only reach it through cipher-emit (§4), and cipher-emit writes to CIPHER-LINE.

This is the authoring boundary. Cartridges write cartridge content on the main grid; Cipher speaks on its own line.

Spec Hygiene Rule 6 in CLAUDE.md ratifies this boundary with one sanctioned exception, documented immediately below: the Null cartridge is uniquely authorized to render Cipher glyphs on the primary display as part of its designed gameplay. No other cartridge has or will have this privilege. Any doc that contradicts either the rule OR the single exception is wrong and gets fixed.

3a. Sanctioned Exception — Null’s Main-Grid Cipher Escape

Section titled “3a. Sanctioned Exception — Null’s Main-Grid Cipher Escape”

Exactly one cartridge is authorized to render Cipher glyphs on the primary 80×25 grid: Null. Null’s extended Cipher dialogue is central to its designed gameplay — an operator engagement with the Cipher voice that cannot be reduced to fragments on the auxiliary line without destroying what makes the module work. The boundary is not flexible; it is physical — Null gets the main grid, every other cart uses CIPHER-LINE exclusively.

The mechanism is a cartridge capability-flag system:

Cart capability block. The .kn86 header (ADR-0006) gains an optional cart-capabilities block listing keywords the cart is requesting. For v0.1, exactly one capability keyword exists:

  • cipher-main-grid-escape — grants access to the cipher-emit-main-grid FFI primitive.

nOSh-runtime-maintained allowlist. Which carts may request which capabilities is gated by a nOSh runtime allowlist baked in at runtime-build time (not runtime-mutable, not operator-configurable). For launch, the allowlist is:

Cart identifierGranted capabilities
nullcipher-main-grid-escape
(all other carts)(none)

A cart that declares a capability it has not been granted is rejected at cart-load with :capability-not-granted. This is a hard error, not a silent drop — the operator should know their cart is malformed or unauthorised.

FFI surface extension. A granted capability unlocks additional FFI primitives beyond the base set enumerated in §4:

  • cipher-main-grid-escape grants cipher-emit-main-grid <string> <row> <col> — renders a Cipher fragment at the specified main-grid cell with standard Cipher typography (monochrome amber #E6A020, optional typewriter animation). Wraps across rows as needed. Respects the row-0/row-24 boundaries (rows 0 and 24 are nOSh-runtime-owned regardless of capability grant). Calling this primitive without the capability granted is a runtime error (:capability-denied); the nOSh runtime rejects the call and the standard error pipeline reports it.

The standard cipher-emit primitive (§4) targets CIPHER-LINE unconditionally. It is unchanged, un-gated, and available to every cart.

Why capability flags instead of per-primitive allowlists. Capability flags compose and scale. If a future cart earns a similar sanctioned exception (e.g., a late-game module that needs to override the status row), it declares the appropriate capability keyword and the nOSh runtime allowlist opens one more slot. Per-primitive allowlists would require a nOSh runtime code change for every new exception. Capability flags are data, not code.

Cross-reference. The cart-format change (adding cart-capabilities to the .kn86 container) was specified as a follow-on amendment to ADR-0006 and landed on 2026-04-24 — see ADR-0006 §Cart-Capabilities Block and its Amendment Log 2026-04-24. Concretely: capabilities live in a new CART_CAPABILITIES static-data subsection (type=5), the nOSh runtime loader validates against a baked-in allowlist with :capability-not-granted as the hard-error path, and Null’s cipher-main-grid-escape is the v0.1 launch allowlist entry. This ADR committed to the surface shape; ADR-0006 now carries the serialization detail.

CIPHER-LINE extends the NoshAPI FFI surface enumerated in ADR-0005. Eight new all-carts primitives land under two groups: Cipher (cartridge contribution to the Cipher voice) and Aux (direct write access to the contextual Row 4 surface). All primitives are Tier 1 (all-carts); none are mission-gated.

Lisp nameSignatureSemantic contractRaises
cipher-emit(fragment-string) → nilRequest the Cipher voice speak fragment-string on CIPHER-LINE Row 2. The nOSh runtime decides whether to speak (mode weights, coherence-stack dedup, reputation-tier filter); if it speaks, the previous Row 2 drops to Row 3 and the new fragment lands at Row 2. Fragment is rendered through the Cipher typewriter (per UI Design System timing class “Deliberate”). Cart does not get back a success/failure signal — the Cipher voice is nOSh-runtime-orchestrated and may ignore, rephrase, or queue the fragment for a later opportunity.:fragment-too-long if fragment-string length > 32 chars.
cipher-push-event(event-keyword &optional payload-alist) → nilPush a typed event into the nOSh runtime’s 128-slot event-ring. The nOSh runtime’s Cipher generator reads the ring to produce utterances; this is the cart’s main lever for “make Cipher notice that something happened.” event-keyword is a Lisp keyword (e.g., :threat-spike, :payout-recovered, :operator-idle); payload-alist is an optional association list of event metadata (e.g., ((:threat . 4) (:source . 'ice-breaker))). Stickiness flags :anomalous and :significant in payload-alist mark the event for slower decay — they stay in the ring longer and weight the generator more heavily.:invalid-keyword if event-keyword is not a symbol; :payload-too-large if payload-alist serializes to >64 bytes.
cipher-extend-grammar(block-expr) → nilRegister the cartridge’s grammar contribution with the nOSh runtime generator. block-expr is a Lisp form that declares vocabulary pools and fragment productions for this cart’s domain (e.g., ICE BREAKER registers (:vocabulary (:actors ("ICE" "FIREWALL" "TRACE")) (:productions ((trace-complete) → "TRACE CLEAN")) ). The nOSh runtime merges the block into the generator’s rule set at cart-load; the merge is removed at cart-unload. The cartridge authoring surface is the block-expr shape, not the nOSh runtime generator internals — carts contribute vocabulary and productions, the nOSh runtime owns the meta-grammar, mode selector, and coherence stack.:grammar-parse-error if block-expr is malformed; :grammar-too-large if merged footprint exceeds the per-cart grammar-arena budget (see §Implementation Notes).
cipher-set-mode-weights(weights-alist) → nilBias the nOSh runtime’s mode selector for the current cartridge. weights-alist maps mode keywords to integer weights (e.g., ((:terse . 3) (:editorial . 1) (:silent . 0))). The nOSh runtime applies the weights on top of the reputation-tier default for the active operator. Weights reset to the reputation-tier default at cart-unload.:unknown-mode if a keyword is not one of the known modes; :invalid-weight if a weight is not a non-negative integer.
Lisp nameSignatureSemantic contractRaises
aux-timer-start(&optional label-string) → nilStart (or restart) the gameplay sub-timer displayed on CIPHER-LINE Row 4, with optional label-string (≤12 chars) for the label chip. The nOSh runtime counts up at 1 Hz and renders LABEL MM:SS at Row 4. If no label is supplied, the nOSh runtime renders the default cartridge-name abbreviation. Resolves any existing sub-timer. Does not affect the mission-level timer on Row 1 (nOSh-runtime-owned).:label-too-long if label-string length > 12 chars.
aux-timer-stop() → nilStop the gameplay sub-timer and clear Row 4 back to the nOSh runtime’s contextual-surface default. Safe to call when no timer is active (no-op).
aux-show-seed(seed-bytes) → nilSwitch CIPHER-LINE Row 4 into seed-capture mode. The nOSh runtime renders a compact hex-ish representation of seed-bytes (up to 8 bytes, rendered as up to 16 hex chars with a SEED: prefix). Rebinds the TERM key to “capture this seed into the operator’s quote slots” for as long as Row 4 is in seed mode. Replaces any sub-timer or mission meta that was on Row 4.:seed-too-long if seed-bytes > 8 bytes.
aux-status-render(status-expr) → nilRequest a cartridge-authored status glyph/label pair on Row 1’s TERM-hint chip (right-edge slot only; battery, timer, and mode chips are nOSh-runtime-owned and cannot be overwritten). status-expr is a small Lisp form declaring a glyph index and a ≤6-char label (e.g., (:glyph 17 :label "LINK")). The chip is visible until the next aux-status-render call, or until cart-unload.:chip-full if the TERM-hint slot is already claimed by the nOSh runtime in a mode it refuses to yield (e.g., during boot, system-image-update, or low-battery emergency); :invalid-expr if the form is malformed.

Total new primitives: 8. The NoshAPI surface grows from 54 to 62. ADR-0005’s Tier 1 (all-carts) section gains a new “CIPHER-LINE” sub-section; Tier 2 and Tier 3 are unchanged.

The TERM key’s default function remains “open the terminal surface” (main-grid behaviour, unchanged). When CIPHER-LINE Row 4 is in seed-capture mode (triggered by aux-show-seed), TERM additionally captures the displayed seed into the operator’s quote-slot ring. The key is context-sensitive, not dual-function: there is one binding at a time, and the nOSh runtime’s hold-detection logic routes the press to whichever surface owns TERM for the current frame.

The Input System Architecture doc needs a small update to describe the seed-capture overlay. That update is out of scope for this ADR and is delegated to the Gameplay Design agent’s follow-up pass (see §Documentation Updates).

The Universal Deck State in docs/architecture/KN-86-Capability-Model-Spec.md gains three fields in service of the Cipher voice’s coherence, the event ring, and the mode selector. These are all nOSh-runtime-owned; cartridges never write them directly.

  • coherence-stack — a 5-slot ring of the most recent Cipher utterances the nOSh runtime has spoken, persisted across cartridge swaps. The Cipher generator reads this ring to avoid obvious repetition across carts (e.g., ICE BREAKER and Cipher Garden both describing a phase debrief should not both say “CLEAN EXTRACTION”). Each slot holds a truncated fragment hash (8 bytes) and a timestamp delta (2 bytes) — 50 bytes total.
  • event-ring — a 128-slot decay-weighted event pool, fed by cipher-push-event and runtime-internal event sources. Each slot holds a keyword hash (4 bytes), a payload hash (4 bytes), a decay counter (1 byte), and a stickiness flag byte — 1280 bytes total (128 × 10). Anomalous and significant events decay more slowly (see cipher-push-event semantics).
  • cipher-mode-weights — 4 bytes of cartridge-tunable mode weights, overlaying the reputation-tier defaults for the currently loaded cart. Reset to reputation defaults at cart-unload. The existing cipher_seed[4] LFSR-state field is unchanged.

The capability-model deck-state schema update that lands this ADR is my scope. The grammar architecture (nOSh runtime meta-grammar, cartridge contribution shape, merge semantics) and the cartridge-contribution block in the same Capability Model Spec is the Gameplay Design agent’s scope — we have agreed not to touch each other’s sections. See §Documentation Updates for the split.


Option A: Stay on main grid, add a Cipher-dedicated region

Section titled “Option A: Stay on main grid, add a Cipher-dedicated region”

Carve a dedicated row band (e.g., Rows 20–22) on the 80×25 main grid, reserve it for Cipher, and write the scheduler to protect it from cartridge overrides.

Rejected because: this solves the authoring-boundary problem (cartridges still don’t get to speak as Cipher) but leaves the surface-scarcity problem unsolved — we’d be giving up 3 of the 23 content rows to nOSh runtime narration, permanently, for every cartridge. The 23-row canvas is already tight on every module Round 2 evaluated; carving it further is a direct regression. It also doesn’t give utility surfaces (battery / timer / seed) a home.

Push Cipher and utility surfaces into a compositor layer over the 80×25 grid — the nOSh runtime draws on top of cartridge text in BITMAP mode.

Rejected because: it violates ADR-0014’s integer-scale, square-pixel, monochrome rendering pipeline. A compositor needs transparency, which monochrome OLED-style rendering doesn’t support without anti-aliasing — which ADR-0014 rejected on art-direction grounds. It also creates a surface no cart can predict against (“the nOSh runtime might be painting on top of you right now”) which breaks the cartridge authoring contract.

Option C: Auxiliary OLED display on SPI (ACCEPTED)

Section titled “Option C: Auxiliary OLED display on SPI (ACCEPTED)”

Mount a small dedicated OLED above the key plate. The nOSh runtime owns the surface end-to-end; cartridges reach it only through the NoshAPI primitives in §4. Main-grid canvas stays at 80×25 with zero Cipher-carve-out.

Chosen because: it solves the three forcing functions simultaneously (Cipher gets out of cartridges’ way, utility surfaces get a home, the authoring boundary becomes physical — two surfaces, two APIs, one device). The cost is one additional peripheral, ~$15 BOM, ~10–30 mA typical draw, and an inset-panel cutout. All three are acceptable against the Q4 2027 ship target and the 3000 mAh pack envelope.


DimensionA (dedicated rows)B (compositor)C (aux OLED, ACCEPTED)
Main-grid content rows available to cartridges20 (down from 23)23 (but with unpredictable nOSh-runtime overpaint)23 (unchanged)
Authoring boundarynOSh-runtime honour systemnOSh-runtime-owns-all-pixelsPhysical — two surfaces
BOM cost$0$0+$15 (SSD1322 3.12” module)
Power budgetno changeno change+10–30 mA typical / +40 mA peak
Mechanical / caseno changeno changeinset-panel cutout; Pelican shell untouched
Fits ADR-0014’s integer-scale / monochrome rulesyesno (compositor demands transparency)yes (separate surface with its own native rendering)
Utility surfaces (battery / timer / seed) solvedpartially (still sharing carved rows)partiallyyes (Row 1 + Row 4)
Cartridge authoring contract preservedyesno (nOSh runtime overpaints)yes (FFI-mediated)

The cost of C is entirely dollar, watt, and panel-cutout. None of those costs compromise a CLAUDE.md spec value; the power-draw delta nudges the runtime band toward the low end of its existing envelope but stays inside it.


  • Main grid stays 80×25 with 23 content rows. Every gameplay spec, screen design, and wireframe keeps its canvas intact. Cartridges do not have to re-plan around a carve-out.
  • Cipher voice gets a home. It is audible (via PSG tone) and visible (via CIPHER-LINE) on a surface the cartridge cannot touch, which matches the fiction (a system-level colleague, not a cart character).
  • Utility surfaces get a home. Battery glyph, timer, mode chip, TERM hint, seed capture, contextual meta — all land on CIPHER-LINE Rows 1 and 4 without stealing cartridge rows.
  • Authoring boundary is physical. text-* primitives write to the main grid; cipher-* and aux-* primitives write to CIPHER-LINE. No ambiguity; no “which surface did I just target?” questions at cart-author time.
  • Grammar split clarifies responsibility. The nOSh runtime owns generator + meta-grammar + mode selector + coherence stack. Cartridges contribute vocabulary pools and fragment productions. This is the authoring surface of the Cipher voice, not nOSh runtime internals, and it scales cleanly across 14 modules.
  • +$15 BOM per unit. Material, not margin; lines up with the existing BOM’s approach of commodity modules.
  • +10–30 mA typical draw. Runtime band nudges from ~20 h toward ~19 h at the low end of the typical envelope. Acceptable; re-measure at bring-up.
  • Inset-panel cutout. New mechanical surface; requires CAD update on the printed panel. Pelican shell itself is not touched.
  • 15 gameplay specs need revision. Every launch-title gameplay spec that currently prescribes “Cipher appears on Row N” or “Cipher ticker at Row 21” is stale. This is the Gameplay Design agent’s scope, not this ADR’s, but the work is real and non-trivial. See §Documentation Updates.
  • Universal Deck State schema grows. coherence-stack + event-ring + cipher-mode-weights add ~1334 bytes to the DeckState struct. Deck-state flash page budget (4 KB region per Capability Model Spec) absorbs the growth without wear-leveling changes.
  • TERM key becomes context-sensitive. Input System Architecture gains a seed-capture overlay binding. Small but real change.
  • 54 → 62 NoshAPI primitives. ADR-0005 gains 8 primitives. Documentation-only delta; the FFI implementation work is queued as follow-on.
  • F1. Emulator oled.c surface. The emulator must render CIPHER-LINE to a dedicated SDL2 window or texture region that tracks the 256×64 panel geometry. C Engineer scope, post-ADR.
  • F2. nOSh runtime driver stack. SPI0 init, SSD1322 init sequence, framebuffer push, dirty-rect optimisation. nOSh runtime implementation work, post-ADR.
  • F3. Grammar framework implementation. nOSh runtime generator + meta-grammar runtime + cart-contribution merge. Gameplay Design agent’s grammar architecture edit to Capability Model Spec comes first; C Engineer implements after.
  • F4. Gameplay-spec revision pass. All 15 gameplay specs (14 launch + THRESHOLD) revised to remove main-grid Cipher and add CIPHER-LINE integrations where relevant. Gameplay Design agent scope.
  • F5. UI Design System + Bare Deck Terminal Spec updates. Retire Row 21 Cipher ticker from the HOME box; retire Cipher-voice announcement blocks from Detail View; add CIPHER-LINE reference surfaces. Gameplay Design agent scope.
  • F6. GPIO pin finalization. DC and RST pin assignments above are TBD during bring-up. First prototype build closes these.
  • F7. Mechanical CAD update. Inset panel gains a CIPHER-LINE bezel cutout. Dimensions TBD during mechanical bring-up.

Documentation Updates (REQUIRED — part of the decision, not aspirational)

Section titled “Documentation Updates (REQUIRED — part of the decision, not aspirational)”

Every file below must change in the same PR that lands this ADR, or in an explicit follow-on PR from the named agent. The audit agent enforces this list; failing to tick a box is treated as a live contradiction per Spec Hygiene Rule 3.

  • CLAUDE.md — new Auxiliary Display row in Canonical Hardware Specification; Keys row updated for TERM context-sensitivity; Case row updated for CIPHER-LINE bezel cutout; new Spec Hygiene Rule 6 (“CIPHER is OLED-exclusive”); nOSh bullet updated; Two Compile Targets paragraph updated; Emulator Architecture diagram gains oled.c. Landed by PM on parent branch claude/wonderful-jones-960564. This agent does not duplicate those edits in its isolated worktree; they exist upstream.
  • docs/architecture/adr/ADR-0015-cipher-line-auxiliary-display.md — this file.
  • docs/hardware/KN-86-Pi-Zero-Build-Specification.md — Hardware Topology diagram gains the CIPHER-LINE OLED module; Subsystem roles table gains an Auxiliary Display row; Section 3 Power topology gains the CIPHER-LINE power-draw delta and the runtime-band nudge; Section 4 Assembly Plan gains a Stage 1b (“Auxiliary display bring-up”) covering SPI0 wiring, SSD1322 init, and bezel-cutout install.
  • docs/hardware/KN-86-Pi-Zero-Sourcing-Guide.md — BOM gains the SSD1322 3.12” 256×64 yellow module (Waveshare, Adafruit, AliExpress candidates). Josh’s verbatim spec quoted.
  • docs/KN-86-Platform-Design-Master-Index.md — ADR-0015 row added to Part II ADR list; CIPHER-LINE added as a platform component in the architecture summary.
  • docs/architecture/KN-86-Capability-Model-Spec.mddeck-state schema portion only. The DeckState struct gains coherence_stack[50], event_ring[1280], and cipher_mode_weights[4]; the “What Each Field Does” section gains entries for each; the reserved-bytes tail shrinks to match. Scope discipline: the grammar-framework portion of this same file (the Cipher Voice section, the Cartridge–nOSh Runtime Interface grammar block, the CIPHER_DOMAIN macro region) is the Gameplay Design agent’s scope — this ADR does not touch it.

Delegated to Gameplay Design agent (separate PRs)

Section titled “Delegated to Gameplay Design agent (separate PRs)”
  • docs/architecture/KN-86-Capability-Model-Spec.md — grammar-framework portion: nOSh runtime meta-grammar description, mode selector, coherence-stack consumer semantics, cartridge-contribution block shape, merge semantics.
  • docs/ui-design/KN-86-UI-Design-System.md — retire main-grid Cipher announcement blocks (§Detail View narrative content, §Modal dialog Cipher variants, §Long-form reading view Cipher variants); add CIPHER-LINE reference patterns.
  • docs/ui-design/KN-86-Bare-Deck-Terminal-Spec.md — retire Row 21 Cipher ticker from HOME box; retire the §“CIPHER” tab layout in its current form; redirect Cipher voice access to CIPHER-LINE. Status / Lambda / Link / SYS tab set unchanged.
  • docs/ui-design/KN-86-Screen-Design-Principles.md — retire the §Cipher Ticker sub-section (Row 21, 20 chars/sec crawl); update the typography-class table and speed-class table to remove Cipher-voice examples from the main-grid mode.
  • docs/ui-design/KN-86-Input-System-Architecture.md — TERM key gains a seed-capture overlay binding when CIPHER-LINE Row 4 is in seed-display mode.
  • docs/ui-design/KN-86-Marty-Glitch-Visual-Prompt.md — the Marty Glitch animation is the Cipher persona override; its voice channel moves from the main grid to CIPHER-LINE.
  • 15 gameplay specs under docs/gameplay-specs/ — every reference to Cipher-on-main-grid replaced with either a CIPHER-LINE reference or a silent-Cipher fallback. The enumerated stale-reference list in §Stale CIPHER References appendix of this ADR is the handoff.

Delegated to C Engineer agent (separate PRs, post-grammar)

Section titled “Delegated to C Engineer agent (separate PRs, post-grammar)”
  • kn86-emulator/src/oled.c (new) — SDL2 render surface for CIPHER-LINE.
  • kn86-emulator/src/nosh.c — wire the 8 new FFI primitives to NoshAPI.
  • kn86-emulator/src/types.h — Auxiliary-display constants and struct fields.
  • nOSh runtime SPI0 + SSD1322 driver (post-prototype bring-up).

A PR that lands this ADR without the first six boxes ticked fails review. The Gameplay Design and C Engineer boxes close in their own PRs after their agents finish the scoped work.


cipher-extend-grammar merges a cart’s grammar block into the nOSh runtime’s generator at cart-load. The merge lives in a dedicated Fe arena per ADR-0004’s arena discipline — separate from the cart’s main Lisp arena so that a chunky grammar block can’t starve the cart’s runtime heap. The per-cart grammar arena is sized at 8 KB for v0.1; carts that exceed the budget at merge time get :grammar-too-large and the cart-load fails cleanly. This budget is revisitable once we have two or three launch carts compiled.

The coherence-stack holds the last 5 utterances; stickiness is time-delta, not count-based. When the Cipher generator evaluates a candidate utterance, it compares the candidate’s truncated fragment hash against the stack; a hit within the last 10 minutes suppresses the candidate with probability 0.9, within the last 60 minutes with probability 0.5, older with probability 0.1. The numbers are tuning knobs, not fixed — they live in the nOSh runtime and are adjustable at boot config, not exposed through the FFI.

Events in the 128-slot ring decay every 15 seconds of wall-clock time. Normal events decay by 1 per tick; :significant events decay by 1 every 3 ticks; :anomalous events decay by 1 every 10 ticks. An event at decay 0 is reaped and its slot freed. The generator weights an event by decay / max_decay when producing utterances, so newer events dominate. These numbers are also tuning knobs.

We could have packaged everything under a single oled-* prefix. We chose cipher-* and aux-* to express intent: cipher-emit means “the Cipher voice is involved, with all its nOSh-runtime-owned coherence gates”; aux-show-seed means “direct cartridge-authored render to the auxiliary display’s contextual surface, no coherence involved.” The prefix tells the cart author which system they’re talking to.


With ADR-0015 approved and merged, implementation work is tracked in the Notion Tasks database under the KN86 tag. Initial queue:

#TaskOwnerDependencies
1Implement CIPHER-LINE emulator subpanel (oled.c + SDL rendering)C Engineer— (foundational)
2Wire 8 new CIPHER-LINE NoshAPI FFI primitives in nosh.cC Engineer#1
3Implement nOSh runtime CIPHER grammar (generator, mode selector, coherence stack, event ring)C Engineer#2
4Amend ADR-0006 (cart format v2) to serialize cart-capabilities header blockEmbedded Systems— (parallel)
5Clean up post-v0.1 plan drift for CIPHER-LINEDocs— (parallel)

Follow-on tasks not yet queued (will be created when the prerequisites land):

  • cipher-main-grid-escape capability enforcement — the cart-capabilities parser + allowlist check + cipher-emit-main-grid FFI primitive. Blocked on task #4.
  • nOSh runtime base meta-grammar — the default vocabulary pools and production fragments the nOSh runtime ships without any cartridge contribution. Blocked on task #3.
  • Null main-grid Cipher dialogue authoring — Null’s extended Cipher sequences, authored as cartridge Lisp using cipher-emit-main-grid. Blocked on the capability enforcement task and Null’s general Lisp authoring readiness.
  • Hardware bring-up sub-tasks (GPIO DC/RST finalization, bezel cutout CAD, power-draw measurement at Stage 1b) — queued against the first-prototype build, targeting the Q4 2027 hardware ship window.

  1. DC and RST GPIO pin finalization. GPIO24 (RST) and GPIO25 (DC) are the current proposals. A conflict with a peripheral we haven’t inventoried (e.g., a future rumble-motor driver) would push them. Flagged TBD during bring-up.

  2. Bezel cutout dimensions. The 256×64 panel has a published mechanical drawing, but the cut-line on the printed inset panel depends on a physical measurement against the panel CAD. Flagged TBD during mechanical bring-up.

  3. Actual power draw. The 10–30 mA typical / 40 mA peak estimate is a datasheet-plus-SSD1322-driver-application-note extrapolation. Real draw on our brightness setting and update cadence is TBD — measured at Stage 1b of the Assembly Plan. If real draw exceeds 40 mA peak by a material margin, CLAUDE.md’s Battery row gets revisited.

  4. Grammar framework details. nOSh runtime meta-grammar shape, mode selector algorithm, cart-contribution block syntax — all Gameplay Design agent scope and not finalized in this ADR. This ADR commits to the split (nOSh runtime meta + cartridge fragments) and the FFI shape; the rules themselves follow.

  5. Refresh rate / dirty-rect. The nOSh runtime driver should push only dirty rows to the panel to keep I/O cost low. The mechanism (per-row or per-tile dirty tracking) is a driver-implementation choice and not load-bearing at the ADR level.

  6. Cart-capabilities serialization (§3a follow-on to ADR-0006): RESOLVED 2026-04-24. Landed as the 2026-04-24 amendment to ADR-0006 (see §Cart-Capabilities Block). The capability block is a new CART_CAPABILITIES static-data subsection (type=5), the nOSh runtime loader validates the block at cart-load against a baked-in allowlist, and unauthorized declarations are rejected with :capability-not-granted on Row 24 of the primary display. Null → cipher-main-grid-escape is the v0.1 launch allowlist entry; every other cart is granted zero capabilities.


The Cipher voice was always going to be a problem on the main grid. Every module Round-2 review flagged the same tension from a different angle: ICE BREAKER wanted the rows Cipher was taking for ticker and debrief; Black Ledger wanted a persistent audit-state line Cipher kept overwriting; Bare Deck had a Row 21 Cipher ticker that was beautiful in isolation and visually crowded the instant a cartridge loaded on top of it. The forcing function wasn’t any one review — it was all of them, saying the same thing in fourteen different voices. Josh approved the auxiliary display on 2026-04-24 after that signal hit critical mass, and the decision was a clean one: Cipher gets its own surface; utility surfaces get a home on the same surface; the authoring boundary becomes physical (two panels, two APIs, one device). A future reader should take away four things: (1) the main grid stays 80×25 with 23 content rows — nothing carved out of the existing canvas; (2) the auxiliary display is nOSh-runtime-owned end to end, and cartridges reach it only through the FFI in §4; (3) the nOSh-runtime/cartridge split on Cipher grammar is generator + meta-grammar + mode selector + coherence stack on the nOSh runtime side, vocabulary pools + fragment productions on the cartridge side; (4) this ADR is the single authoritative statement on CIPHER-LINE’s hardware, layout, and cartridge-facing surface — any doc that contradicts it is wrong and must be fixed, not forked.