Skip to content

NoshAPI — Primitives by Category

The NoshAPI is the full-runtime FFI surface that kn86emu exposes to cartridge Lisp. ADR-0005 enumerated 54 primitives across three capability tiers (all-carts, mission-context, REPL-read-only); ADR-0007 layered on the mission-script grammar; runtime evolution has added CIPHER-LINE, nEmacs, graphics, sound, save, provenance, snippet, and panic primitives. This page is the implementer-facing categorized index for that full surface.

kn86-nosh-tb, the ADR-0027 termbox2 spike runtime, intentionally does not expose this whole table. Its cartridge contract is the constrained cell API plus five semantic callbacks documented in cell-api.md.


MarkerMeaning
v1Stable, in ADR-0005 v1.0 enumeration. Cartridges may rely on signature and semantics.
v1+Stable, added by an accepted ADR after ADR-0005 v1.0 (e.g. ADR-0016’s emacs-extend-*). Treat as v1.
evolvingSpecified but the signature or semantics may shift before launch. Cartridge authors should expect possible breaking changes.

Type tags follow ADR-0005’s table — Bool, Number, String, Symbol, Cell (opaque), Bytes (byte array), Unit (returns nil).


HostExposed surfaceUse today
kn86emuFull NoshAPI listed belowSDL desktop emulator, launch-cart compatibility, full SystemState integration.
kn86-nosh-tbConstrained cell API and on-* callbacksTermbox2/device spike, terminal-native carts, chrome-ownership validation.

Do not assume a cart written for one host can run unchanged on the other. The .kn86 container is shared, but the Fe symbols bound by the host differ.


Primary 80×25 SDL text grid. Cartridges may not write Row 0 or Row 24 (firmware-owned). This section describes the full kn86emu surface; the termbox2 path uses the 128×75 constrained cell API instead.

Lisp nameSignatureReturnsSemanticsStability
text-clear() UnitClear text framebuffer (does not affect bitmap area).v1
text-putc(col row char)UnitPlace a single character at (col, row); ASCII 0–127.v1
text-puts(col row string)UnitPrint null-terminated string at (col, row), left-aligned, clipped at grid boundary.v1
text-printf(col row fmt arg...)UnitPrintf-style formatted text; supports %d %u %x %c %s; max 128 bytes.v1
text-scroll(lines)UnitScroll text up (positive) or down (negative); range −25 to +25.v1
text-cursor(col row visible)UnitShow / hide text cursor at (col, row).v1
text-invert(col row len)UnitInvert text color for len characters; emphasis primitive.v1

Display: bitmap (960×600 logical framebuffer)

Section titled “Display: bitmap (960×600 logical framebuffer)”

Per ADR-0014, the canvas is 960×600 logical pixels (the 1024×600 Elecrow has 32 px horizontal letterbox per side that cartridges never see). All bitmap primitives below are bounds-checked against 960×600.

Lisp nameSignatureReturnsSemanticsStability
gfx-clear()UnitClear bitmap framebuffer.v1
gfx-pixel(x y on)UnitSet / clear pixel; x 0–959, y 0–599.v1
gfx-line(x0 y0 x1 y1)UnitBresenham line.v1
gfx-rect(x y w h filled)UnitFilled or outline rectangle.v1
gfx-circle(cx cy r)UnitCircle outline at center (cx, cy).v1
gfx-blit(x y bytes w h)UnitBlit packed 1-bpp bitmap (byte length = (w*h+7)/8).v1
Lisp nameSignatureReturnsSemanticsStability
split-view(bitmap-rows)UnitSet split-view: top bitmap-rows pixels are graphics, remainder text.v1
display-mode()SymbolQuery current mode: :text, :graphics, or :split.v1

YM2149 emulation via the runtime’s psg.c. Direct register access is provided for advanced use; most carts use the higher-level helpers.

Lisp nameSignatureReturnsSemanticsStability
psg-write(reg val)UnitWrite PSG register reg (0–14) with val (0–255). Direct hardware-style access.v1
psg-read(reg)NumberRead current value of register.v1
Lisp nameSignatureReturnsSemanticsStability
sound-tone(channel freq vol)UnitPlay tone on channel 0–2 at frequency (Hz) and volume 0–15.v1
sound-noise(period)UnitSet noise period (0–31).v1
sound-envelope(shape period)UnitSet envelope shape (0–15) and period.v1
sound-silence()UnitMute all channels and noise.v1
Lisp nameSignatureReturnsSemanticsStability
sfx-keyclick()UnitShort click; key-confirmation feedback.v1
sfx-boot()UnitAscending boot tones (~300 ms).v1
sfx-select()UnitSelection beep.v1
sfx-confirm()UnitConfirmation tone.v1
sfx-error()UnitError buzz.v1
sfx-alert()UnitAlert ping.v1

Lisp nameSignatureReturnsSemanticsStability
spawn-cell(type-symbol)CellAllocate cell of given type; type must be registered in cart init.v1
destroy-cell(cell)UnitReturn cell to free pool; handle becomes invalid.v1
Lisp nameSignatureReturnsSemanticsStability
drill-into(cell)UnitPush cell onto nav stack; fires ON_EXIT then ON_ENTER.v1
navigate-back()UnitPop nav stack; fires lifecycle handlers.v1
current-cell()CellTop of nav stack, or nil if empty.v1
set-root(cell)UnitReset nav stack to cell; usually called once at cart init.v1
next-sibling()UnitMove to next sibling in current list (no-op at last).v1
prev-sibling()UnitMove to previous sibling (no-op at first).v1
Lisp nameSignatureReturnsSemanticsStability
list-push(parent cell)UnitAppend cell as child of parent.v1
list-get(parent index)CellGet Nth child (0-indexed); nil if out of range.v1
list-length(parent)NumberCount children.v1
is-leaf(cell)BoolTrue if cell has no children.v1
link-cells(parent child)UnitEstablish parent/child relation; equivalent to list-push.v1
unlink-cell(cell)UnitRemove from parent + sibling chain; orphaned but not destroyed.v1

Lisp nameSignatureReturnsSemanticsStability
lfsr-seed(seed)UnitInitialize 32-bit LFSR.v1
lfsr-next()NumberAdvance LFSR; return next pseudo-random 32-bit value.v1
lfsr-range(min max)NumberPseudo-random integer in [min, max] inclusive.v1
lfsr-shuffle(array-length)UnitFisher-Yates shuffle in-place. Per ADR-0005 §“Known Unknowns” #1, the Lisp surface for in-place array mutation is unresolved; treat as evolving.evolving

Cart-facing surface for the 2× trackpoint hardware committed in ../../../adr/ADR-0032-sweep-peripheral-commitment.md. Both physical trackpoints aggregate at the master KB2040 over QMK split-pointing-device transport and emit as one logical cursor — carts see the merged cursor in cell coordinates on the main 80×25 grid. Cursor renders on cartridge content rows (1–23) only; clamped at Row 0 / Row 24 boundaries per CLAUDE.md Spec Hygiene Rule 5; does not render on the CIPHER-LINE auxiliary OLED. Visibility defaults to visible; cart hides via the :pointer-hidden manifest flag (cart-load default) or cursor-visible! runtime FFI. Click semantics (tap-vs-drag threshold) are firmware-defined per ADR-0032 §4; right-click and middle-click are deferred to v0.2. Per-trackpoint differentiation (pointer-a / pointer-b) is a v2 question. See ../../../adr/ADR-0035-trackpoint-cart-ffi.md for the full decision record.

Lisp nameSignatureReturnsSemanticsStability
cursor-position()(col row) (2-element list)Query current cursor cell on the main 80×25 grid; col 0–79, row 1–23. Logical position is independent of visibility — hidden cursor still has a position. O(1); never raises.v1+
on-trackpoint-move(handler)UnitRegister handler invoked on cell-boundary crossing; handler signature (handler col row delta-col delta-row). One handler per cart; pass nil to unregister. Sustained against-boundary push does not re-fire the handler.v1+
on-trackpoint-click(handler)UnitRegister handler invoked on click; handler signature (handler col row). v0.1 fires exactly when QMK emits a primary-button click — no button-index argument; right-click / middle-click deferred to v0.2. One handler per cart; pass nil to unregister.v1+
cursor-visible!(visible)UnitShow (#t) / hide (#f) the cursor for the lifetime of the cart load. Overrides the manifest :pointer-hidden default. Independent of position.v1+

Available to all carts. Read-only on this tier; mutation requires mission context.

Lisp nameSignatureReturnsSemanticsStability
deck-state()CellReference to Universal Deck State (record-style accessor).v1
get-handle()StringOperator handle, max 16 chars.v1
get-credits()NumberCurrent credit balance.v1
get-reputation()NumberCurrent reputation points.v1
has-capability(bit-index)BoolTest cartridge-history capability bit (0–31).v1

Lisp nameSignatureReturnsSemanticsStability
draw-threat-bar(level max col row)UnitDraw level/max filled blocks; max 16 cells wide.v1
draw-progress-bar(pct col row width)UnitDraw percentage-filled bar; width 1–32 cells.v1
draw-bordered-box(col row w h title)UnitBox outline with optional centered title.v1

Mission context (Tier 2 — active phase only)

Section titled “Mission context (Tier 2 — active phase only)”

Available only when a mission phase is active; calls outside mission context raise :not-in-mission.

Lisp nameSignatureReturnsSemanticsStability
phase-advance()UnitAdvance to next phase in chain; calls lifecycle handlers; auto-completes at last phase.v1
mission-complete()UnitMark mission complete; award payout; pop mission context.v1
award-credits(amount)UnitAdd (or subtract, if negative) credits to deck balance.v1
modify-reputation(delta)UnitAdjust reputation; clamped at [0, 32767].v1
set-phase-data(key value)UnitStore key-value in current phase’s persistent data (survives phase boundaries within the mission).v1
get-phase-data(key)AnyRetrieve phase data; nil if unset.v1
set-capability(bit-index)UnitSet cartridge-history bit; marks the module as completed.v1

Scripted-mission-only context (per ADR-0007)

Section titled “Scripted-mission-only context (per ADR-0007)”
Lisp nameSignatureReturnsSemanticsStability
mission-input()AnyInput value provided by mission template.v1
mission-context()AnyLocal mission state (read-only).v1
mission-deck-state()AnyRead-only deck-state snapshot (Tier 2 grant).v1
current-mission-phase()SymbolPhase metadata (Tier 2 grant).v1
cartridge-data(cart-id)AnyRead from another loaded cartridge (Tier 2; requires :grants clause).v1
pass()UnitAcceptance contract: signal mission success.v1
fail(clause...)UnitAcceptance contract: signal failure with clause-level diagnostics.v1

Per software/runtime/cipher-voice.md §11. CIPHER is OLED-exclusive (Spec Hygiene Rule 6). The Null cartridge has a sanctioned main-grid escape; no other cart may bypass.

Lisp nameSignatureReturnsSemanticsStability
cipher-emit(mode-or-nil &optional :event ev)UnitPush a mode hint or forced silence into the next tick; optionally bind a synthetic event.v1
cipher-push-event(event-record)UnitPush event onto stream + memory store; runtime stamps :t and :tag.v1
cipher-extend-grammar(grammar-fragment)BoolAdd a cipher-grammar block at runtime; only legal during :mission-brief / :bare-deck beats.v1
cipher-set-mode-weights(beat delta-list)UnitTemporary mode-weight bias scoped to current mission or cart unload.v1
cipher-stack-head(n)ListRead top N entries of coherence stack.v1
cipher-main-grid-escape()UnitNull cartridge only — sanctioned exception to OLED-exclusive rule. Rejected for any other cart.v1
aux-timer-start(:tag :duration-ms :on-fire ev)NumberStart runtime-managed countdown; expiry pushes event; max 600,000 ms. Returns timer handle.v1
aux-timer-stop(handle)BoolCancel timer; #t if live, #f if already fired.v1
aux-show-seed(seed-bytes :label string)UnitFreeze CIPHER-LINE Row 1 to a captured seed; enter seed-capture mode.v1
aux-status-render(row content :priority pri)UnitRender to CIPHER-LINE Row 1 or Row 4; tickers if overflow; priority arbitrates concurrent writers.v1

Cartridges contribute to nEmacs / REPL completion via two FFI primitives extending ADR-0009’s static ranking model:

Lisp nameSignatureReturnsSemanticsStability
emacs-extend-grammar(sexp)UnitCart contributes grammar productions for legal-form filtering in the predictive palette.v1+
emacs-extend-vocabulary(list-of-strings)UnitCart contributes domain terms; each receives the +5 vocabulary boost in token ranking.v1+
prompt-text(prompt max-len)StringModal text-entry dialog (Nokia multi-tap on CIPHER-LINE Row 4); returns the entered string or nil on cancel.v1+

Lifecycle: panic / cart-isolation (GWP-320)

Section titled “Lifecycle: panic / cart-isolation (GWP-320)”

Cart-issued clean self-halt. Routes through fe_error so it shares the recovery flow with arena exhaustion and any other Fe-side abort. The runtime captures a panic record, unloads the cart, posts a “CART HALTED” notice on CIPHER-LINE Row 4, writes a log file, and returns to bare-deck Terminal. See ../../runtime/panic-recovery.md.

Lisp nameSignatureReturnsSemanticsStability
panic(msg)does not returnHalt this cart. msg is captured into the panic record’s reason field.v1+
cart-panic(msg)does not returnAlias of panic; both bind to the same cfunc.v1+

Per ADR-0005 §“Tier 3,” the player REPL exposes only the read-only subset of the above:

  • Display: display-mode.
  • State: get-handle, get-credits, get-reputation, has-capability, deck-state.
  • Cells (inspect): current-cell, list-get, list-length, is-leaf.
  • Procedural: lfsr-seed, lfsr-next, lfsr-range.

All mutating primitives — text-*, gfx-*, sound-*, spawn-cell, destroy-cell, drill-into, navigate-back, phase-advance, mission-complete, award-credits, modify-reputation, set-capability, cipher-push-event, aux-* — are forbidden in the REPL. Player REPL is for inspection and scripted automation, not for mutating mission state.


Per software/runtime/orchestration.md §“Graceful Degradation” — the runtime’s contract for partial-hardware failure. Cart authors read this table to know how each primitive behaves when a subsystem (coprocessor link, microSD, battery) is unavailable. The runtime never raises a cart-visible exception for hardware degradation; the doctrine is “show what you have, label what you don’t, never panic.” Carts that need explicit degradation handling subscribe to runtime events (e.g., low-battery-event); they do not wrap each FFI call.

Primitive groupSubsystemDegradation behavior
psg-write, psg-read, sound-tone, sound-noise, sound-envelope, sound-silence, sfx-keyclick, sfx-boot, sfx-select, sfx-confirm, sfx-error, sfx-alertCoprocessor link (audio path)Silent no-op when the bridge is in KN86_LINK_DEGRADED. psg-read returns 0. Runtime renders a LINK? indicator on the firmware Row 0 status bar; cart sees no error.
cipher-emit, aux-timer-start, aux-timer-stop, aux-show-seed, aux-status-renderCoprocessor link (OLED path)Silent no-op for the render side. Runtime-side accounting continues: cipher-push-event still mutates the UDS event ring; aux-timer-start still returns a valid handle and the runtime still fires expiry, only the OLED render of the result is suppressed.
cart_save, cart_loadCart microSD filesystemReturn Bool per the existing signature. #f means the underlying write/read failed (cart pulled mid-write, corrupted filesystem, write-protected media). Already part of the FFI contract; degradation doctrine does not add new return shapes.
deck-state, get-handle, get-credits, get-reputation, has-capabilityDevice microSD (UDS persistence)Always succeed against the in-memory UDS struct. The runtime handles the persistence path; cart reads are always served. If device microSD becomes unreadable, runtime degrades persistence to session-only and surfaces a bare-deck status line — cart-facing reads are unchanged.
All primitivesLow batteryRuntime caps animation to 15 fps and dims CIPHER-LINE at the warning threshold; on the critical threshold, runtime pushes (low-battery-event :level :critical :remaining-pct N) to subscribed carts only, then begins clean shutdown. Carts that do not subscribe see no change in their event stream.
All primitivesEmulator window < 80×25Inapplicable. Emulator exits cleanly with a stderr message before reaching cart load. Device firmware cannot enter this state (fixed Elecrow 7” IPS framebuffer).

For the full degradation-mode spec — triggers, runtime behaviors, recovery paths, and per-mode summary table — see software/runtime/orchestration.md §“Graceful Degradation”. For per-primitive C-side mechanics, see kn86-emulator/src/coproc.c (the vtable seam that drops audio/OLED calls when degraded).


The CIPHER-LINE event-record schema, the cart-format cipher-grammar block layout, and the Lisp ↔ C type marshalling table all live where they’re authoritative — not duplicated here. See:

  • Event records, modes, grammar productions: ../../runtime/cipher-voice.md.
  • Cart-format binary layout for grammar/vocabulary blocks: adr/ADR-0006-cart-format-v2.md (filesystem-versioned).
  • Type mapping (C ↔ Lisp): ADR-0005 §“Type Mapping.”
  • Open questions (array mutation FFI, error trace depth, mission-context shape): ADR-0005 §“Known Unknowns / Follow-ups.”
  • Hardware-degradation contracts (coprocessor offline, microSD absent, low battery, emulator window too small): software/runtime/orchestration.md §“Graceful Degradation” and §“Degradation contracts” above. Panic recovery (cart-internal SIGSEGV / fe_error / (panic)) lives in software/runtime/panic-recovery.md and is distinct.