ADR-0040: DeckRunner — the named engine layer
Context
Section titled “Context”For a year the runtime has been described as “nOSh, the orchestrator.” That single word hid two different layers: the platform floor that drives the hardware (renderer, audio, input mechanism, integrity cores, the host seam), and the engine that runs the game on top of it (the run loop, the screen router, Mission Control, Mission Runner, the cart loader). Conflating them has three concrete costs:
- The run loop has no clear owner. “What owns the game loop?” has no clean answer — and in fact the loop body (
nosh_host_step) is stranded in the emulator’s SDLmain.c, fused withSDL_PollEvent, never lifted intolibnosh. The device host can only paint a boot HUD because the engine tier was never separated from a host. - The player-facing scripting surface has no name. ADR-0029’s Mission Runner bindings and the REPL console verbs are the surface untrusted player Lisp runs against — but there is no named boundary for it, so its trust contract is implicit.
- Gameplay-architecture decisions had nowhere to live. The 2026-06-18 design session fixed the state model, the verb/loadout model, the capability-baseline model, and the keyboard model — all of which describe the engine, an unnamed thing.
Forcing functions
Section titled “Forcing functions”- The loop lift needs an owner. Moving
nosh_host_stepout of the host requires naming what it moves into. - Mission Control (ADR-0028) and Mission Runner (ADR-0029) already exist as named subsystems, but the layer they live in does not. They need a parent that is not “nOSh the whole library.”
- The session produced a coherent block of decisions (state tiers, two FFI surfaces, verb model, loadout, keyboard) that interlock and need one durable home.
Constraints
Section titled “Constraints”- Do not rename or supersede Mission Control / Mission Runner. They are shipped and correct; the engine name must contain them, not replace them.
- No code moves in this ADR. Like ADR-0039, this is a naming/architecture decision; the loop lift and the terminology sweep are tracked follow-ons.
- No canonical hardware-spec change. All hardware values remain as in CLAUDE.md; this ADR references them.
Decision
Section titled “Decision”Adopt DeckRunner as the named engine layer of the KN-86 runtime — the tier between the Platform and Content that owns the run loop and contains the mission subsystems. Specifically:
- DeckRunner is the engine tier (inside
libnosh, beside the Platform floor — not a new repo). It owns the deck run loop, the screen router, the cart loader/executor, and the two FFI surfaces. It contains Mission Control (ADR-0028) and Mission Runner (ADR-0029) as subsystems — Mission Control is the front of a run (generate + pick a contract), Mission Runner is the back (execute it), DeckRunner is the engine both live inside. - Loop ownership: the Platform owns the
while(timing, frame pacing, present); DeckRunner owns the body (step). The host-facing engine API is the existingnosh_host.h, renamed and owned:init/load_cart/step/on_signal/shutdown+ UDS access. Liftingnosh_host_stepout of the hostmain.cintolibnoshis the gating follow-on. - Three-tier state model. Run state (volatile — the gameplay; player/REPL read+write), Config (
nosh-config.toml— toggleable), Deck State / UDS (durable, game-significant — read-only to the player, written only by DeckRunner as the earned consequence of a sanctioned outcome). Durable progress changes as a result, never as a typed command. - Two FFI surfaces. NoshAPI is cart-facing (ADR-0005). The DeckRunner interface is player-facing — the console verbs + scripting API (it includes the Mission Runner bindings). Player Lisp calls only the DeckRunner interface; DeckRunner re-exports selected NoshAPI on its own terms; player code never reaches raw NoshAPI or the Platform. The DeckRunner interface is the sandbox wall for untrusted player code.
- The device is a fantasy console, not a terminal app. It draws RGB565 pixels to the framebuffer (ADR-0036); the “terminal” is an aesthetic + a layout convention. The REPL is DeckRunner’s engine console (Quake-style dropdown, scriptable) — and, with a contract active, it is the Mission Runner.
- Keyboard: anchors + layers. The invariant is a small set of anchor keys (BACK / EVAL-ENT / SYS + the layer thumbs) that never change meaning, plus a layered remainder DeckRunner swaps by context (Nokia/T9 text · symbols · Lisp primitives · verb palettes · app chords). This refines ADR-0022’s absolute “the 14 primitives never remap”: the grammar (anchors + the primitive layer) is fixed; the vocabulary (verb-tools) layers. QMK holds layers electrically; DeckRunner picks which is live.
- Capability model: baseline vs cart-deepened. DeckRunner ships shallow, generic, first-party base apps (the Capability-Registry System tier, ADR-0030); a cart deepens its domain (augments verbs, unlocks the full interface). Enhanced capability is managed via Lambda Slots (a limited loadout) with full-swap semantics and a coupled time↔exposure swap cost.
- Ratify the companion spec.
software/runtime/deckrunner-engine-architecture.mdis canonical for the detail of items 1–7 and carries the run-execution pipeline, the verb-tier model, and the open reconciliation items.
Options Considered
Section titled “Options Considered”Option A: Name the engine layer (DeckRunner) + ratify the companion spec. (ACCEPTED)
Section titled “Option A: Name the engine layer (DeckRunner) + ratify the companion spec. (ACCEPTED)”Name the tier that owns the run loop and contains Mission Control + Mission Runner; record the session’s interlocking decisions in a companion spec this ADR ratifies.
Chosen because it gives the loop lift a destination, gives the player-facing surface a named trust boundary, and gives the gameplay-architecture decisions one durable home — without disturbing the shipped Mission Control / Mission Runner vocabulary or moving any code.
Option B: Leave the engine layer unnamed (“nOSh, the orchestrator”).
Section titled “Option B: Leave the engine layer unnamed (“nOSh, the orchestrator”).”Keep one word for the whole runtime; describe the engine behaviour in prose where needed.
Rejected because the conflation is exactly what produced the stranded loop and the unnamed trust boundary. An unnamed layer cannot own the loop lift or the player-facing FFI contract; the costs in Context recur.
Option C: Reuse “Mission Runner” or “Mission Control” as the engine name.
Section titled “Option C: Reuse “Mission Runner” or “Mission Control” as the engine name.”Promote one of the existing subsystem names to mean the whole engine.
Rejected because both are parts of the engine with precise, shipped meanings (ADR-0028/0029). Overloading either to also mean the containing tier would blur a boundary that is currently clean — the engine needs a distinct name that contains them.
Trade-off Analysis
Section titled “Trade-off Analysis”| Dimension | A — name DeckRunner (chosen) | B — leave unnamed | C — reuse Mission Runner/Control |
|---|---|---|---|
| Gives the loop lift a destination | ✓ | ✗ | ◐ ambiguous |
| Names the player-facing trust boundary | ✓ DeckRunner interface | ✗ implicit | ◐ overloaded |
| Preserves Mission Control / Runner meaning | ✓ contains them | ✓ (unchanged) | ✗ overloads |
| Home for the session’s decisions | ✓ companion spec | ✗ scattered | ◐ awkward fit |
| Code moved by this ADR | ✓ none | ✓ none | ✓ none |
| Cost | ◐ a terminology sweep (phase-gated) | ✓ none now | ✗ re-blurs a clean boundary |
Cost: a doc-and-code terminology sweep to call the engine tier “DeckRunner” — phase-gated, exactly as ADR-0039’s renames are (decision now, sweep follows).
Consequences
Section titled “Consequences”Positive
Section titled “Positive”- The run loop has a named owner; the loop lift (
nosh_host_step→libnosh) now has a destination and is unblocked. - The player-facing surface (the DeckRunner interface) is a named, sandboxed trust boundary — sibling to NoshAPI.
- The state model makes the player console structurally unable to be a cheat console (UDS is write-only via sanctioned outcomes).
- The session’s gameplay-architecture decisions have one canonical home.
- Mission Control and Mission Runner keep their exact shipped meaning, now with a named parent.
Negative / Accepted costs
Section titled “Negative / Accepted costs”- A terminology sweep (“nOSh the orchestrator / engine” → “DeckRunner”) across docs and code — phase-gated, not fix-now.
- The DeckRunner interface needs its own FFI spec + an ADR-0005 amendment before it is implementable; today it exists only as the Mission Runner bindings.
- One open reconciliation remains: where loadout/swap management sits relative to ADR-0029’s opaque
(load-capability …)boundary (companion spec §12, §16).
Follow-on work this ADR creates
Section titled “Follow-on work this ADR creates”- Lift the run loop into
libnosh(nOSh) — implementnosh_host_step()out of the emulatormain.c. Highest priority; gates the device. - Terminology sweep — name the engine tier “DeckRunner” across
kn86-docs, and update CLAUDE.md’s runtime sections (kinoshita repo). - DeckRunner interface FFI spec + ADR-0005 amendment — the player-facing surface as a named boundary.
- ADR-0022 refinement note — record the “anchors + layers” refinement of the “never remap” framing.
- Gameplay design — swap-currency numbers, trackpoint bindings, and the loadout/
load-capabilityreconciliation (companion spec §16).
Documentation Updates (REQUIRED — Spec Hygiene Rule 3)
Section titled “Documentation Updates (REQUIRED — Spec Hygiene Rule 3)”-
docs/software/runtime/deckrunner-engine-architecture.md— NEW companion spec (this PR). -
docs/adr/ADR-0040-deckrunner-engine.md— this file. -
docs/adr/README.md— index entry added at the top of the active list.
Grep result (Spec Hygiene Rule 3). “DeckRunner” is a new coinage (no prior usage in the corpus). This ADR is additive and moves no code, so references that describe “nOSh, the orchestrator/engine” remain accurate descriptions of the current monolithic naming until the terminology sweep lands — each is a tracked phase-gated follow-on, not a fix-now error (same disposition as ADR-0039’s rename sweep):
- Engine-tier naming sweep —
software/runtime/orchestration.mdand the runtime docs call the orchestrator “nOSh.” Reframe to “DeckRunner (the engine tier of nOSh)” — follow-up PR. - CLAUDE.md runtime sections (kinoshita repo) — “nOSh is the orchestrator” → name the DeckRunner tier. Follow-on (different repo).
- ADR-0022 amendment note — the “never remap” framing refined to “anchors + layers.”
- DeckRunner interface — new FFI spec + ADR-0005 amendment (follow-up PR).
Narrative
Section titled “Narrative”ADR-0028 named what makes contracts. ADR-0029 named what runs them. Neither named the thing they live inside — the engine that owns the frame loop, routes between screens, loads carts, and hands the player a sandboxed console. For a year that thing was just “nOSh,” the same word used for the whole library and its hardware floor, and the ambiguity had a price: the game loop ended up stranded in a desktop host because there was no engine to put it in. This ADR names the engine DeckRunner and draws the line that was missing — the Platform drives the hardware and owns the clock; DeckRunner owns the tick, contains Mission Control and Mission Runner, and exposes two surfaces, one to carts and one to the player. No code moves here; the loop lift and the sweep follow. But the next time someone asks “what owns the game loop?”, there is finally a name to answer with.