Skip to content

Default On-Device Capabilities — Cross-Capability Interaction Plan

Scope: Coordinates the four nOSh-runtime-owned (nOSh) default capabilities — not cartridge-level features. Four per-capability design docs feed into this synthesis:

Backlog representations of the deferred implementation work: GWP-165 (Mission Board), GWP-166 (Cipher), GWP-168 (REPL), GWP-169 (nEmacs). This plan does not restate the Canonical Hardware Specification — reference that doc for grid / row / arena values.

CIPHER-LINE supersession note (2026-04-24). ADR-0015 (the CIPHER-LINE auxiliary OLED) and its companion docs/architecture/KN-86-CIPHER-LINE-Grammar-Framework.md retired every main-grid Cipher surface that earlier drafts of this plan assumed — the MISSIONS-tab Rows 9–12 Cipher rail, the full-screen debrief Cipher passage on Rows 1–23, and the editor-overlay Cipher ambient line on Row 22. The Cipher voice now renders exclusively on CIPHER-LINE (Rows 2–3 scrollback, Row 4 contextual) per the Grammar Framework. CLAUDE.md Spec Hygiene Rule 6 ratifies this boundary with a single sanctioned exception (Null’s cipher-main-grid-escape); no other default on-device capability renders Cipher glyphs on the main grid. Sections 2, 3, and 6 of this plan were revised in GWP-211 to reflect the new render surface.


1. Capability responsibilities at a glance

Section titled “1. Capability responsibilities at a glance”
CapabilityOwnerArenaPrimary roleOwned data
Mission BoardnOSh runtimemission_board struct + event busProcedural contract generation, board UI, resume slotActive contract instances, board seed state, resume-slot metadata
Cipher voicenOSh runtimeevent ring + coherence stack + merged grammar arena (per-cart ≤8 KB); renders to CIPHER-LINE OLED, not the main gridStructured-event-driven fragment composer keyed to deck state (five modes: observe / annotate / reflect / drift / silent) per Grammar FrameworkEvent ring (128 slots), coherence stack (5 slots × fragment hash + time delta), cipher_mode_weights, merged grammar frame (firmware universals + every loaded cart’s cipher-grammar block)
dev REPLnOSh runtime24 KB arena (active only)In-deck Lisp REPL over Fe VMHistory cell (persisted to deck state), tutorial cursor, scratch env
nEmacsnOSh runtime16 KB arena (active only)Structural S-expression editor + snippet libraryEdit buffer AST, palette state, snippet library index (persisted)

All four share a single nOSh event bus for cross-capability signalling. No capability polls another’s struct directly, and no capability mutates another’s state.


2. Event bus — canonical event names and ownership

Section titled “2. Event bus — canonical event names and ownership”

Publishers own event names and payload field shapes. Subscribers bind without coupling to publisher internals.

  • mission_board_refreshed { board_hash, contract_count }
  • mission_highlight_changed { contract_id, threat_level, phase_count, domain_tags[], payout_credits, narrative_seed, requires_swap }
  • mission_bid { contract_id }
  • mission_accepted { contract_id, scripted, template_handle, grants_mask }
  • mission_suspended_resume_available { mission_id }
  • mission_required_cartridge_missing { contract_id, missing_domain_tag }
  • mission_phase_started { mission_id, phase_index }
  • mission_phase_completed { mission_id, phase_index, outcome }
  • mission_phase_transition_waiting { mission_id, next_phase_cart_tag }
  • mission_completed { mission_id, outcome, reputation_delta, payout_delta }
  • mission_abandoned { mission_id, reason }
  • mission_script_failed { mission_id, failed_clauses[] }
  • vocab_frame_updated { frame_hash, classes[], domain_tags[] } on cartridge insert/eject. Merged frame = firmware universals + all loaded-cart defdomain rows.
  • cartridge_inserted { cart_id, manifest }
  • cartridge_ejected { cart_id }
  • nemacs_script_submitted { contract_id, script_source, script_bytecode_handle }
  • nemacs_script_abandoned { contract_id }
  • cipher_on_editor_milestone { kind, context } where kind ∈ {FIRST_LAMBDA_DEFINED, LONG_EDIT_NO_EVAL, CONTRACT_FAILED_3_TIMES} — low-frequency ambient signal Cipher may choose to narrate or ignore
  • cipher_line_painted { event_type, mode, fragment_hash, seed_after } — informational, used by QA replay and the REPL scrollback mirror. Fires after each CIPHER-LINE Row-2/Row-3 repaint; includes the Grammar Framework mode that produced the fragment (:observe / :annotate / :reflect / :drift) and the post-advance cipher_seed value. Silent mode does NOT emit this event (no paint, nothing to record).
  • repl_entered / repl_exited — consumed by Mission Board and nEmacs to enforce arena mutual exclusion

Direction: Mission Board → Cipher (Cipher is a pure subscriber here; it never mutates board state).

Cipher subscribes to all mission_* events listed above. On each, the nOSh runtime invokes cipher-push-event with the structured event record (type, affect, payload, narrative seed); the Cipher engine runs its mode selector against the current beat and event ring, elects a mode (:observe / :annotate / :reflect / :drift / :silent), and — when not silent — expands the grammar against the merged vocab frame to produce a CIPHER-LINE fragment. See Grammar Framework §§3, 5, 7 for the event-to-utterance pipeline; Mission Board → Cipher event typing is tabled in docs/plans/post-v0.1/2026-04-21-mission-board.md §Cross-capability hooks → Cipher voice.

Determinism fix: Every MissionInstance carries a narrative_seed distinct from cipher_seed. Cipher accepts the seed as an event-payload argument and uses it to seed a per-contract scratch LFSR, so board-driven commentary does not advance the deck’s global LFSR. This preserves QA replay: given a fixed event sequence and a fixed cipher_seed, CIPHER-LINE output is deterministic per Grammar Framework §9 Determinism.

Zones: Cipher renders on CIPHER-LINE Rows 2–3 (scrollback) — not the main grid. Per ADR-0015 and CLAUDE.md Spec Hygiene Rule 6, there is no MISSIONS-tab Cipher annotation rail on the 80×25 display, and there is no main-grid Cipher debrief modal on mission_completed. The Mission Board draws nothing on CIPHER-LINE; its only route to Cipher is the event stream, and the CIPHER-LINE engine owns rendering on that surface. Phase-completion Cipher commentary rides a :result-success / :result-failure event under the :debrief beat and fires on CIPHER-LINE like any other Cipher utterance — see docs/plans/post-v0.1/2026-04-21-cipher-voice.md §5.3 for the post-debrief-modal rationale.

Direction: both.

REPL → Board: read-only introspection primitives.

(mission-board) → list of (id threat payout phase-count domains)
(mission-board-contract id) → full descriptor
(mission-active?) → boolean
(mission-board-peek n) → n-contract summary (used also by Cipher mirror)
(mission-board-gen) → generation metadata (seed, template ref)
(mission-regenerate-preview seed rep balance caps overlay) → dry-run, does not advance global state

Board → REPL: when a scripted mission is accepted with scope grants_mask, REPL binds one additional permission-gated primitive inside the mission scope:

(submit-solution! expr) ; bound only inside scripted-mission scope

Blocked at REPL (always raise '(not-authorized outside-mission :fn X)): mission-accept!, mission-board-refresh!, anything mutating deck state. Per the REPL agent’s visible-symbol policy, these remain bound for (describe 'X) discoverability but error on invocation.

mission-simulate-accept hint-integrity question: keep behind a debug flag, off by default. Will be re-evaluated when scripted missions ship.

Direction: both. This is the scripted-mission loop — the most complex pair.

Board → nEmacs on scripted-mission acceptance:

nemacs_load_mission(
mission_id,
buffer_seed, /* starter AST, empty or template */
contract_fn, /* validates output */
error_scope_fn, /* returns node range for error highlighting */
grants_mask, /* ADR-0007 FFI tier */
mission_input /* passed to the form as bound value */
)

nEmacs → Board on EVAL:

  • Contract pass → nemacs_script_submitted → firmware fires mission_accepted{ scripted: true }mission_phase_started etc.
  • Contract fail → mission_script_failed{ failed_clauses[] } → nEmacs highlights offending node via error_scope_fn.
  • Abandon (BACK past root or SYS → abandon) → nemacs_script_abandonedmission_abandoned.

Resume: nEmacs owns writes to the resume_slot in deck-state flash. Mission Board owns reads. On cartridge_ejected mid-edit, nEmacs force-tears-down and serialises the AST to the resume slot; Board fires mission_suspended_resume_available when the required cart returns.

Palette enrichment (separate from the mission loop): on cartridge_insertedvocab_frame_updated, nEmacs rebuilds its palette ranking with the new domain_tags[] for the +5 domain boost.

Direction: both.

REPL → Cipher primitives:

(cipher-sample :template ...) ; read-only, scratch LFSR, no seed advance — for tutorials
(cipher-generate :seed s :template t) ; pure render, deterministic, no seed advance
(cipher-vocabulary) ; merged vocab frame
(cipher-seed) ; read LFSR state non-mutatively

Blocked at home REPL: cipher-narrate (the side-effecting one that advances the deck cipher_seed and pushes a fragment onto CIPHER-LINE Row 2). It unlocks only inside the Null module’s mission scope as (cipher-monologue :topic ...) per the Null gameplay spec; Null’s monologue may also render on the main grid via cipher-emit-main-grid, authorized by the cipher-main-grid-escape capability (ADR-0015 §3a — the one sanctioned exception to CLAUDE.md Rule 6).

Cipher → REPL: REPL scrollback mirrors all mission_* and cipher_line_painted events as ;-commented log lines so operators can paste full board/Cipher context into bug reports. No direct RPC.

Direction: both, low-bandwidth.

Cipher → nEmacs:

  • Both subscribe to vocab_frame_updated. Cipher uses the merged frame for passage composition; nEmacs uses it for palette ranking. Same frame_hash on both sides.
  • cipher_domain_tokens(cart_id) FFI from nEmacs into Cipher, used only to materialise the domain-tags list at palette construction.

nEmacs → Cipher:

  • cipher_on_editor_milestone { kind, context } — fires rarely, Cipher decides whether to render an ambient fragment on CIPHER-LINE. LONG_EDIT_NO_EVAL and CONTRACT_FAILED_3_TIMES are the candidate ambient triggers. Cipher may stay silent (default for operators with cipher-silent=true or during active scripted missions).
  • On mission_script_failed, the mission’s contract_fn can optionally tag the event with a hint fragment which the Cipher engine surfaces at debrief time.

Never: Cipher does not render inside the editor code buffer (Rows 1–21) and there is no main-grid Cipher ambient overlay on Row 22. Ambient passages, if rendered, appear on CIPHER-LINE Rows 2–3 like any other Cipher utterance — per ADR-0015 and CLAUDE.md Rule 6, there are no main-grid Cipher surfaces inside nEmacs (nEmacs is not the sanctioned exception; Null is). See docs/plans/post-v0.1/2026-04-21-cipher-voice.md §4.2 for the full rationale.

Direction: both, high-bandwidth.

nEmacs → REPL on EVAL:

repl_eval_form(form, grants_mask, timeout_ms, mem_limit_bytes) → value | error

Mission-context EVAL passes the mission’s grants_mask; free-form EVAL passes the home REPL grants. Three arenas may coexist at peak: active cart (~16–32 KB) + REPL (24 KB) + editor (16 KB) ≈ 72 KB of 520 KB SRAM. Safe.

Open question (resolved in this plan, needs ratification): the REPL “evaluator” is a shared service decoupled from the REPL UI. nEmacs can invoke it without “entering” the REPL screen. This is the only way the arena budget works, and both agents assume it.

REPL → nEmacs (the shared snippet library):

(snippet-list) ; read-only, returns names
(snippet-load name) ; read-only, returns source AST
(snippet-save! name form) ; one exceptional write primitive in the REPL
(open-in-nemacs form) ; full-context handoff — REPL tears down, nEmacs opens with form pre-populated

Shared *clipboard* binding is writable from both. Library format is canonical Lisp source (not serialised AST), 32-entry index + blob region, owned by nEmacs.

Arena mutual exclusion: REPL and nEmacs cannot be active simultaneously. repl_entered forces nEmacs teardown with auto-save to resume slot; nemacs_entered does the same to REPL history. Active-cart teardown on either is governed by the cart’s arena size (see §5).


Owner: nOSh core (not any single capability). Computed at cartridge insert/eject from firmware universal tables + every loaded cart’s defdomain rows. Publishes vocab_frame_updated { frame_hash, classes[], domain_tags[] }. Consumers: Cipher composer, nEmacs palette.

Read-only from REPL ((deck-state), (credits), (reputation), (cartridge-history), (cipher-seed)). Writable only via mission context granted primitives or nOSh-runtime-owned transitions. nEmacs does not access deck state directly — everything flows through mission grants_mask.

Owner: nEmacs (storage + index). Readable + writable from REPL via the three primitives above. Format is Lisp source text, survives firmware/VM version drift and enables link-cable portability between decks. 32 entries, flash-backed.

Owner: nEmacs writes, Mission Board reads. One AST blob + mission_id + buffer_seed. Fires mission_suspended_resume_available when the matching cart returns.


Arena coexistence is the single constraint ratified across the four designs. Ratification note to write a new ADR superseding ADR-0002’s softer “suspend” language — flagged by dev REPL agent.

Rules:

  1. At most one of {REPL, nEmacs} is active at any time. Entering one tears down the other, auto-saving the torn-down subsystem’s state (REPL: history to deck-state; nEmacs: AST to resume slot).
  2. Active cart coexists with whichever of {REPL, nEmacs} is live, provided cart arena ≤ 32 KB. Nominal peak: cart 32 + REPL 24 = 56 KB, or cart 32 + nEmacs 16 = 48 KB. Both fit in 520 KB SRAM with headroom for firmware.
  3. repl_eval_form called from nEmacs spins up REPL arena on demand (no UI entry) and frees post-dispatch unless debugger-hold is set (post-v1.1 feature).
  4. Cartridge swap mid-edit is a hard nEmacs teardown trigger — detect-pin lowering raises an interrupt that pre-empts any in-flight form.

6. UI zone map (default on-device surfaces only)

Section titled “6. UI zone map (default on-device surfaces only)”

All zones live within the content area defined in CLAUDE.md. Row 0 (status bar) and Row 24 (action bar) are nOSh-runtime-owned and never drawn by these subsystems directly — they publish hints via nosh_publish_status_hint / nosh_publish_action_hint instead.

The table below covers the primary 80×25 grid. The Cipher voice does not appear on this surface (per ADR-0015 and CLAUDE.md Rule 6, with the single sanctioned Null exception noted below); its render zones live on the CIPHER-LINE auxiliary OLED and are summarized separately.

SurfaceRowsNotes
Mission Board (list)1–22Title row 1, list body rows 2–22
Mission Board inspector (Zone D, inline)14–22First CAR on highlighted contract; does not lose list context
Mission Board inspector (full)1–23Second CAR promotes to full-screen Pattern 5
REPL prompt + scrollback1–23Full content area when REPL is the active surface
nEmacs code buffer1–21AST-rendered with structural indentation
nEmacs palette22–232 rows × 4 slots = 8 visible candidates (reconciled from ADR-0008’s 3-row proposal)
Null’s Cipher main-grid escape1–23The one sanctioned ADR-0015 exception. Null cartridge only, authorized via cipher-main-grid-escape; rendered by cipher-emit-main-grid, not by the nOSh runtime Cipher engine

CIPHER-LINE auxiliary OLED surface. Authoritative layout in ADR-0015 §2 and Grammar Framework §2. Summary here for cross-capability orientation only:

CIPHER-LINE rowContentWriter
Row 1Status strip (battery, timer, mode, TERM-hint chip)nOSh runtime + cart via aux-status-render right-edge chip
Rows 2–3Cipher scrollback (current fragment + previous echo)nOSh runtime Cipher engine; cart contributes events / grammar / mode-bias
Row 4Contextual (sub-timer, seed capture, phase meta, link status)nOSh runtime, optionally biased by cart via aux-* primitives

All Cipher utterances driven by Mission Board, REPL, nEmacs, or firmware lifecycle events land on CIPHER-LINE Rows 2–3, never on the main grid.

Resolution of the palette row contradiction (nEmacs agent’s flagged issue): ADR-0008’s “rows 23–25” violates CLAUDE.md Row 24 firmware ownership. This plan ratifies palette on rows 22–23, key hints published to Row 24 via nosh_publish_action_hint. ADR-0008 needs an amendment (follow-up action).


  1. cartridge_history = uint32_t. Capability-Model-Spec is canon. Bare-Deck-Terminal-Spec’s uint16_t is a stale reference — fix it in a doc-hygiene PR.
  2. Mission Board is a tab. Home-Screen-Design is canon. Capability-Model-Spec’s “primary interface after boot” phrasing needs amending to “primary tab after boot.”
  3. cipher_seed = 32-bit (4-byte LFSR, matches DeckState). Bare-Deck-Terminal-Spec’s “16-bit Galois” is a stale reference — fix it in the same doc-hygiene PR.

These are settled; they are recorded here for future reference and do not require further decision.

  1. Memory orchestration: tear-down-and-restart, not suspend. Exiting REPL or nEmacs drops the arena; any user-facing state worth keeping was serialised first (REPL history → deck state; nEmacs AST → resume slot). Simpler code, predictable memory, frees SRAM for the active cartridge. A new ADR superseding ADR-0002’s softer “suspend” language is a follow-up doc task — not a blocker for implementation.
  2. ADR-0008 palette rows: 22–23, not 23–25. Row 24 is nOSh-runtime-owned per CLAUDE.md. ADR-0008 amendment is follow-up doc hygiene.
  3. Scripted-mission submission routes through nEmacs. REPL’s (submit-solution!) remains as a non-scripted shortcut for dev use. Agents converged on this; no conflict.
  4. LAMBDA on Mission Board: drop the predicate-macro proposal. Keep LAMBDA’s existing 32-step sequence meaning per Input System Architecture. Tag-filtered bidding lives under CONS.
  5. SYS → Refresh Board rep cost = 1 point. Adjust after playtest.
  6. mission-simulate-accept is off by default, gated behind a --dev-mode CLI flag.
  7. cipher-silent is a global manifest flag, not per-passage-type.
  8. Ambient-commentary Row 22 overlay fade vs overwrite: deferred to playtest. Not a pre-implementation blocker.
  9. QUOTE replay in Cipher uses composition tuples, not rendered text. Smaller and composes with later vocab updates.
  10. Null module uses the home REPL with extra primitives bound inside Null mission scope. No dedicated surface.
  11. Tutorial re-entry after wipe: deferred to marketing/packaging. Not a pre-implementation blocker.
  12. PAL-mode palette scroll: QUOTE/LAMBDA (not numpad 2/4, which select slots). Input System Architecture ratifies in the nEmacs implementation wave.
  13. ADR-0008 status: marked “Accepted (v1.0)” but nEmacs slips post-launch per ADR-0002. Doc-hygiene fix.
  14. Relay tag-collision: cartridge wins. Cartridge is source; Relay is a content-update layer that cannot override author intent.

These happen when the relevant capability promotes out of the deferred catalog. Each gets folded into that capability’s implementation PR.

  • Fix cartridge_history width in Bare-Deck-Terminal-Spec (from §7.1.1).
  • Fix cipher_seed width in Bare-Deck-Terminal-Spec (from §7.1.3).
  • Amend Capability-Model-Spec’s “primary interface after boot” → “primary tab after boot” (from §7.1.2).
  • Write ADR superseding ADR-0002 memory-orchestration “suspend” language with tear-down-and-restart (from §7.2.4).
  • Amend ADR-0008 palette-row mockups to rows 22–23; fix its Accepted status line (from §7.2.5 and §7.2.16).

8. Next steps (when these features promote out of the deferred catalog)

Section titled “8. Next steps (when these features promote out of the deferred catalog)”
  1. Resolve open questions 1–6 first — they block implementation.
  2. Write a new ADR ratifying §5 memory orchestration and §6 palette row resolution.
  3. Write a companion ADR enumerating the full event bus contract in §2.
  4. Update ADR-0008 with a superseding note pointing at this plan.
  5. Engineering can then work the four deferred tasks in dependency order: Cipher grammar (no dependencies) → Mission Board generation (publishes events Cipher needs) → nEmacs (consumes vocab frame + mission dispatch) → dev REPL (ties it all together).
  6. Null module gameplay spec is the natural integration test — it exercises Cipher, REPL, and deck-state reads simultaneously.

Authored by PM synthesis from four parallel design agents: GWP-179 (Mission Board), GWP-180 (Cipher), GWP-181 (REPL), GWP-182 (nEmacs). Individual per-capability docs in this directory are the authoritative sources; this plan is the integration layer.