KN-86 CIPHER-LINE Grammar Framework
Companion ADR: adr/ADR-0015-cipher-line-auxiliary-display.md (hardware, layout, rendering budget)
Superseded behavior: All earlier specs that placed Cipher voice text on the main 80×25 grid. Cipher is OLED-exclusive.
1. Purpose and Aesthetic Target
Section titled “1. Purpose and Aesthetic Target”CIPHER-LINE is the auxiliary OLED display mounted above the keyboard. It is the one and only surface on which the Cipher voice appears. The main 80×25 grid is reserved for cartridge content, firmware HUD rows (Row 0 status, Row 24 action), and diegetic system output. Cipher does not draw to the main grid. Cartridges do not draw to CIPHER-LINE except through the sanctioned NoshAPI primitives defined here.
Aesthetic Target
Section titled “Aesthetic Target”Cockpit voice recorder. Ship’s log in Alien. Onboard system narrating itself.
Not HAL-style dialogue. Not a chat partner. Not commentary in the LP-stream sense. The Cipher is a deck subsystem that clips observations, tense beats, and memory fragments into the operator’s peripheral vision while they work the main grid. It is felt more than read. Operators who stare at it are doing it wrong; operators who catch a fragment in their peripheral and understand it are doing it right.
Non-Goals
Section titled “Non-Goals”- Full sentences. If Cipher sounds like a chatbot, the grammar failed.
- Dialogue interaction. CIPHER-LINE is one-way output. Operators do not “talk to” Cipher. (The Null module’s extended Cipher engagement is a special case — still one-way, just with more surface area.)
- Decoration. Silence is a first-class mode. A CIPHER-LINE that is quiet during a tense beat is working as designed.
- Narrative exposition. Cipher observes, annotates, reflects, drifts. It does not explain plot.
Voice Heuristic (for writers, including cart authors)
Section titled “Voice Heuristic (for writers, including cart authors)”Full sentences read like chatbot. Clipped observational fragments read like an onboard system narrating. “this corridor. same hum.” is right. “The current corridor reminds me of sector nine.” is wrong.
A fragment passes the voice heuristic if it meets at least three of the following:
- Under eight words in a single clause unit.
- Drops articles (
the,a) where they would be natural in prose. - Drops connectives (
and,but,because). - Uses present or past-participle verbs rather than complete indicative clauses.
- Could plausibly be read aloud in a single breath.
Applied to the grammar: observe and annotate modes should rarely violate (1); reflect and drift are allowed longer fragments but still trend short.
2. CIPHER-LINE as Output Surface
Section titled “2. CIPHER-LINE as Output Surface”See CLAUDE.md Canonical Hardware Specification for the authoritative hardware values (auxiliary display, font rendering, row count, per-row character budget, TERM key context-sensitivity). This framework treats the display as a fixed output surface with the following logical rows:
| Row | Content | nOSh runtime or Cart |
|---|---|---|
| 1 (top) | Status strip — time, link state, cart tag, deck handle, mode marker | nOSh runtime |
| 2 | Cipher scrollback line A (current fragment) | nOSh runtime (Cipher engine) |
| 3 | Cipher scrollback line B (previous fragment echo) | nOSh runtime (Cipher engine) |
| 4 (bottom) | Contextual — chord hints, seed capture prompt, swap countdown, silence | nOSh runtime, optionally biased by cart |
Cartridges do not write pixels to CIPHER-LINE. Cartridges contribute event records, grammar fragments, vocabulary, and mode-weight biases; the nOSh runtime renders. This enforces one voice across every cartridge, which is the point.
Sanctioned Exception — Null’s Main-Grid Cipher Escape
Section titled “Sanctioned Exception — Null’s Main-Grid Cipher Escape”Cipher output is normally confined to CIPHER-LINE. There is one sanctioned exception: the Null cartridge may render Cipher glyphs on the main 80×25 grid as part of its designed gameplay.
The exception is enforced by a nOSh runtime allowlist, not by convention. Null’s .kn86 container carries a capability declaration. In the packager’s authoring form, this reads as:
(cart-capabilities (cipher-main-grid-escape))On disk, this declaration is serialized into a CART_CAPABILITIES static-data subsection (type=5) in the .kn86 container — ADR-0006 §Cart-Capabilities Block is the authoritative binary format. The Lisp form above is the author-facing intent; the binary form is what the nOSh runtime loader parses.
Only Null is granted cipher-main-grid-escape by the nOSh runtime allowlist. Any other cartridge that declares the capability is rejected at load-time with :capability-not-granted. With the capability granted, Null gains access to one additional FFI primitive — (cipher-emit-main-grid <string> <row> <col>) — which renders a Cipher fragment onto the main grid at the specified row/col using the same amber typography as CIPHER-LINE (Rows 1–23 are cartridge-usable; Row 0 and Row 24 remain nOSh-runtime-owned). The standard (cipher-emit ...) primitive still targets CIPHER-LINE unconditionally and is available to all cartridges, including Null, for peripheral / ambient voice.
This is the only mechanism by which Cipher glyphs reach the main grid. All other cartridges use CIPHER-LINE primitives exclusively.
The voice heuristic (§1) still applies to main-grid escape output. Null’s main-grid passages may run longer than a single CIPHER-LINE fragment — they are designed to be studied at length — but the aesthetic stays onboard narrator, not chatbot. Main-grid passages are chains of clipped fragments joined by punctuation and line breaks, not conversational sentences. The capability exists to give Null’s contemplative gameplay surface area, not to invite dialogue.
See adr/ADR-0015-cipher-line-auxiliary-display.md §3a for the surface mechanism (allowlist structure, rejection flow, FFI primitive signature) and adr/ADR-0006-cart-format-v2.md §Cart-Capabilities Block for the binary serialization of the capability declaration.
3. Event Stream
Section titled “3. Event Stream”The Cipher engine does not read cartridge state directly. Cartridges and the nOSh runtime push structured event records into a shared event stream. The grammar consumes events and produces utterances.
Record Format
Section titled “Record Format”(:event :type <symbol> ; required :actor <symbol-or-string> ; optional — primary participant :target <symbol-or-string> ; optional — secondary participant / object :location <symbol-or-string> ; optional — where :from <symbol-or-string> ; optional — movement source :to <symbol-or-string> ; optional — movement destination :t <timestamp> ; required — monotonic deck clock (millis since epoch fold) :tag <cartridge-id> ; required — which cart emitted, or :firmware :affect (<tag>...) ; optional — see Affect Tags below :weight <uint8> ; optional — base memory-store weight (default 16))Standard Event Types
Section titled “Standard Event Types”Cartridges may introduce their own :type symbols (registered via cipher-grammar — see §7). The nOSh runtime understands the following baseline set and uses them for phase-chain and economy-level utterances:
:type | Meaning | Typical affect |
|---|---|---|
:movement | Operator traverses a cell or sector boundary | :routine |
:observation | Operator OBSERVEs — pressed INFO, opened scope, scanned | :routine |
:contact | New entity enters scope (ICE, wreck, contact, packet, sentry) | :tense, sometimes :anomalous |
:action | Operator EVALs a tool, fires a countermeasure, commits an extraction | varies |
:result-success | Action succeeded | :significant |
:result-failure | Action failed | :significant, often :tense |
:threat-rise | Trace/threat/heat/heat-equivalent crossed an upward threshold | :tense |
:threat-fall | Trace/threat/heat crossed a downward threshold | :routine |
:phase-advance | Multi-phase mission crossed into the next phase | :significant |
:cart-swap | Cartridge physically exchanged mid-mission | :significant, :anomalous |
:mission-start | Contract accepted, first phase begins | :routine |
:mission-end | Contract complete, payout recorded | :significant |
:idle | Operator has not provided input for idle-threshold ticks | :routine |
:anomaly | Anything the cart considers strange for its domain | :anomalous |
Affect Tags
Section titled “Affect Tags”Affect tags modify memory-store weight and mode-selector bias. The baseline set:
| Tag | Effect on memory weight | Effect on mode selector |
|---|---|---|
:routine | 1× (baseline) | Slight observe bias |
:tense | 1.5× | Strong observe bias; suppresses drift |
:significant | 2× | Moderate reflect bias; enables cross-cartridge recall |
:anomalous | 3×, sticky | Unlocks drift; biases annotate; strongest recall priority |
:quiet | 0.5× | Strong silent bias (use sparingly; reserved for reverent moments) |
Multiple tags compose multiplicatively on weight and additively on mode bias. (:tense :significant) on a late-phase contact is the canonical “this matters” signal.
Timestamp Semantics
Section titled “Timestamp Semantics”:t is the monotonic deck clock in milliseconds since the device’s last power-on (or, for long-running sessions, since the last deck-state epoch fold — the nOSh runtime refolds every 2^32 ms, about 49 days). Two events with :t within 200 ms of each other are considered simultaneous for coherence purposes. The timestamp matters only for relative ordering and decay; Cipher never renders a raw time value.
4. Memory Store
Section titled “4. Memory Store”The engine maintains a fixed-size ring buffer of past events. When the reflection trigger fires, the grammar samples from this pool with probability proportional to a decaying weight.
Data Structure
Section titled “Data Structure”memory-store :capacity 128 ; fixed ring buffer :head <uint8> ; write cursor :count <uint8> ; 0..128 live entries :entries [<event-record>...] ; ringWeight Model
Section titled “Weight Model”Each live entry carries a current weight computed lazily at sample time:
w(event) = base_weight(event) * affect_multiplier(event) * decay(age)
base_weight = event.:weight (default 16)affect_multiplier = product of affect-tag multipliers (see §3)decay(age_ticks) = max(sticky_floor, 1.0 - (age_ticks / 4096))
sticky_floor = 0.50 if any of event.:affect contains :anomalous or :significant 0.15 if event.:affect contains :quiet 0.00 otherwiseage_ticks is measured in Cipher-engine ticks (not wall clock); the engine ticks once per rendered frame of CIPHER-LINE activity or once per second of idle, whichever comes first. This keeps sticky beats around long enough for the operator to return from a sub-task and still catch the reflection.
Eviction
Section titled “Eviction”When the ring is full, new events overwrite the oldest slot unconditionally. Stickiness does not block eviction — it only lengthens the window during which a sticky event dominates sampling. This keeps total memory bounded at 128 × sizeof(event-record) ≈ 3–4 KB, well within the nOSh runtime arena (see ADR-0004 for the VM memory discipline this honors).
Sampling
Section titled “Sampling”On reflect or drift mode, the engine samples one entry e from the live set with probability proportional to w(e). Sampling may exclude the current (most recent) event to ensure reflect genuinely looks back. Sampling biases available to cartridges via cipher-set-mode-weights (§7) include a :recall-domain hint that filters to events emitted by a specific cart tag — “when ICE Breaker fires reflect, prefer to recall its own past events” — without hard-coding the filter.
5. Utterance Modes
Section titled “5. Utterance Modes”Every Cipher tick selects one mode. The selected mode then expands its non-terminal in the grammar.
5.1 observe — report on the current event
Section titled “5.1 observe — report on the current event”Single-line comment on the most recent event. Present tense. The bread and butter of active play.
Canonical examples:
corridor seven. north.(movement event,:routine)contact. bearing east.(sonar contact,:tense)mirror. clean.(tool EVAL,:result-success)trace up.(threat-rise)packet drop. recovered.(packet-loss event in NeonGrid,:routine)
5.2 annotate — comment on an event, referencing its type or affect
Section titled “5.2 annotate — comment on an event, referencing its type or affect”Slightly more editorial than observe. Labels the event without narrating it in full. Used when the beat wants a voice line but the plain observation would be too flat.
Canonical examples:
that one felt wrong.(:anomalouscontact)smooth.(:result-successafter a long:tensestretch)costly.(:result-failure, high stakes)the good kind of quiet.(extended:routinestretch)too clean.(:result-successon an anomalous target)
5.3 reflect — pull a memory, connect it to the present
Section titled “5.3 reflect — pull a memory, connect it to the present”Deictic opener pointing to “now” followed by a fragment from a sampled memory. Chains present and past.
Canonical examples:
this corridor. same hum as sector nine.here again. last time it cost us.this light. seen it before the black ledger run.same pressure signature. the wreck at three-fifty.this silence. before the trace spiked, last time.
5.4 drift — pure memory, no anchor to now
Section titled “5.4 drift — pure memory, no anchor to now”Fragment of a past event without a present-tense hook. The mode the Cipher drifts into during idle or between cartridge swaps. Creates the illusion of a system with an inner life.
Canonical examples:
that relay. three decks ago.the one that didn't ping back.winter contract. payout never cleared.handshake at dawn. nobody's handshake.the operator before this one.(only emittable whencartridge_historyshows a different handle has used the deck)
5.5 silent — emit nothing
Section titled “5.5 silent — emit nothing”First-class register. The engine selected a mode, the mode was silent, and the engine did not render a line. The previous line in Row 2 ticks upward to Row 3 on the next non-silent mode, preserving echo.
Silence is not “no mode ran.” The engine still consumes the event, still advances the tick, still updates the memory store. It chose not to speak. This is the cockpit voice recorder sitting quiet during a tense approach. Cartridges may push silence with (cipher-emit :silent) to force a silent tick on a specific beat.
Mode Semantics Summary
Section titled “Mode Semantics Summary”| Mode | Looks at | Echo cost | Typical beat |
|---|---|---|---|
observe | Current event | Emits to Row 2; previous line shifts to Row 3 | Active play |
annotate | Current event + its affect | Same as observe | Tense moment, small editorial |
reflect | Current event + sampled memory | Same | Transitions, phase breaks |
drift | Sampled memory only | Same | Idle, cart-swap lulls |
silent | — | None (Row 2/3 hold) | Tense beats, reverent moments |
6. Mode Selector
Section titled “6. Mode Selector”Each tick, the engine computes a weighted distribution over the five modes and samples one. The distribution depends on the beat parameter, cartridge biases, affect of the triggering event, and the last-emitted mode.
Beat Parameter
Section titled “Beat Parameter”beat is a nOSh runtime managed symbol that reflects mission phase and deck context:
beat | Default distribution (observe / annotate / reflect / drift / silent) |
|---|---|
:bare-deck | 0.15 / 0.15 / 0.20 / 0.35 / 0.15 — bare deck encourages drift |
:mission-brief | 0.45 / 0.25 / 0.15 / 0.05 / 0.10 — observation-heavy |
:active-hack | 0.60 / 0.20 / 0.05 / 0.00 / 0.15 — observation dominates; silence present |
:high-tense | 0.45 / 0.25 / 0.00 / 0.00 / 0.30 — no drift, silence rises |
:phase-transition | 0.10 / 0.20 / 0.45 / 0.15 / 0.10 — reflection dominates |
:cart-swap-lull | 0.05 / 0.10 / 0.20 / 0.55 / 0.10 — drift dominates |
:debrief | 0.20 / 0.35 / 0.30 / 0.05 / 0.10 — annotate + reflect |
:idle | 0.05 / 0.05 / 0.15 / 0.40 / 0.35 — silence and drift |
Computation
Section titled “Computation”distribution = beat_defaults[current_beat]distribution = apply_cart_biases(distribution, active_cart_biases)distribution = apply_event_affect(distribution, triggering_event.affect)distribution = apply_repetition_penalty(distribution, last_mode)distribution = normalize(distribution)mode = weighted_sample(distribution, lfsr_next(deck_state.cipher_seed))Repetition penalty: The mode emitted on the previous non-silent tick has its probability halved this tick, then renormalized. This reduces “two reflects in a row” without forbidding it. Silent ticks do not count as “last-mode” for this purpose.
Event affect bias: :anomalous subtracts 0.15 from observe, adds 0.10 to annotate, adds 0.05 to drift. :quiet adds 0.20 to silent. :significant adds 0.15 to reflect. These are applied before normalization and can push probabilities below zero, at which point they clamp to zero.
Cartridge Biases
Section titled “Cartridge Biases”Cartridges declare a bias table at load via the cipher-grammar block (§7). Biases are additive deltas applied before normalization. A bias table is keyed by beat and takes effect whenever that cart is the active cart.
Example (ICE Breaker):
(cipher-mode-biases (:active-hack (:observe +0.05 :silent +0.05)) (:high-tense (:silent +0.10 :annotate -0.05)) (:cart-swap-lull (:drift +0.10)))Cartridge biases are capped at ±0.20 per mode per beat so that no cart can drown out another or make a mode disappear entirely.
Determinism
Section titled “Determinism”Mode selection uses the deck-state cipher_seed LFSR, advanced once per mode selection (ADR-0004 memory discipline). Two identical deck states + identical event streams produce identical Cipher output. This is a load-bearing property: Null’s Cipher Analysis bounty (see KN-86-Null-Gameplay-Spec.md) relies on it, and the test harness uses it for golden-file regression.
7. Grammar S-Exp Format
Section titled “7. Grammar S-Exp Format”The grammar is a set of nested s-expressions defining weighted productions. Cartridges ship their grammar extensions in the .kn86 container; the nOSh runtime ships a baseline grammar covering all five modes and the standard event types.
Non-Terminals
Section titled “Non-Terminals”Non-terminals are symbols with a leading colon or written as bare symbols inside grammar blocks. By convention, slot-filling non-terminals (pulled from the current event) use the event-key names: :actor, :target, :location, :from, :to. Memory-drawn non-terminals use :memory-* prefixes.
Productions
Section titled “Productions”A production maps a non-terminal to a list of weighted alternatives:
(<non-terminal> -> (<weight> <expansion>) (<weight> <expansion>) ...)<expansion> is a list of literals and non-terminals. Literals are strings. Non-terminals are symbols starting with :. Weights are positive integers; the engine normalizes per production at load time.
Baseline Grammar (abbreviated)
Section titled “Baseline Grammar (abbreviated)”(utterance -> (1 (:mode-observe)) (1 (:mode-annotate)) (1 (:mode-reflect)) (1 (:mode-drift))); note: mode selection itself is done by the mode selector (§6);; the grammar's (utterance) rule is only used when a rare path; skips the selector (e.g., test harness).
(:mode-observe -> (3 (:subject) " " (:verb-present) " " (:object) (:coda?)) (2 (:subject) ". " (:verb-present) ".") (3 (:location) ". " (:heading?)) (2 (:actor) ". " (:verb-past-participle) "."))
(:mode-annotate -> (3 (:deictic) " " (:affect-word) ".") (2 (:affect-word) ".") (2 "that " (:event-kind) ". " (:affect-word) "."))
(:mode-reflect -> (3 (:deictic) ". " (:memory-fragment) (:coda?)) (2 "same " (:memory-keyword) ". " (:memory-fragment)) (2 (:memory-fragment) ". then. " (:memory-fragment)))
(:mode-drift -> (3 (:memory-fragment) ".") (2 (:memory-deictic) ". " (:memory-fragment)) (1 "the " (:memory-keyword) ". " (:memory-when)))
(:deictic -> (2 "this place") (2 "here") (2 "here again") (1 "same light") (1 "this hour") (1 "this corridor") (1 "this silence") (1 "same pressure"))
(:coda? -> (5 "") (2 ".") (1 " —") (1 " " (:affect-word)))
(:affect-word -> (2 "quiet") (2 "wrong") (2 "familiar") (1 "colder now") (1 "loud") (1 "clean") (1 "dry") (1 "thin"))
(:heading? -> (3 "") (1 "north.") (1 "back.") (1 "still."))
(:event-kind -> ; filled at runtime from event.:type (1 :event-type-slot))Cartridges extend :subject, :object, :location, :verb-present, :verb-past-participle, :memory-keyword, and :event-kind with domain-appropriate vocabulary. Cartridges may also add new non-terminals prefixed with their cart tag (e.g., :ice-breaker/ice-class), which become callable from cart-supplied productions.
Slot Filling
Section titled “Slot Filling”When the engine expands :subject, :object, :location, :from, :to, it pulls from the triggering event’s corresponding field. If the field is missing, it falls back to the cartridge’s default vocabulary for that slot; if the cart has no default, it falls back to the nOSh runtime’s generic vocabulary ("it", "here", "the place", etc.). Slot filling never fails loudly.
:memory-fragment expansion samples a past event and runs it through a reduced grammar — an abbreviated version of :mode-observe — producing a clipped past-tense fragment. This is how reflect and drift speak memory.
Production Weights and Sampling
Section titled “Production Weights and Sampling”Production alternatives are sampled with lfsr_next(cipher_seed) and weight-proportional selection, same mechanism as mode selection. Deterministic. Cart-supplied productions compose into the nOSh runtime’s productions at load time (§7 merge rules below).
8. Style Controls
Section titled “8. Style Controls”Three global parameters modify expansion regardless of mode or grammar. Each is a uint8 in [0, 255] stored in deck state and tunable per beat.
terseness
Section titled “terseness”0 = prose-like; 255 = telegraph.
At low terseness, all productions expand normally. As terseness rises, the engine drops articles (the, a, an) from expansions, drops connectives (and, then, but), and prefers the shortest weighted alternative in every production. At terseness >= 192, codas default to empty and affect-words are preferred over full clauses.
Default by beat: :active-hack = 192, :high-tense = 224, :idle = 128, :debrief = 160.
certainty
Section titled “certainty”0 = nothing is sure; 255 = deck is confident.
At low certainty, the engine inserts hedges (maybe, seems, something, probably) into expansions at sampled points. At high certainty, hedges are suppressed and declarative-voice productions are preferred.
Default by beat: :active-hack = 200, :mission-brief = 160, :anomaly = 96, :cart-swap-lull = 128.
temporal-blur
Section titled “temporal-blur”0 = strict tense; 255 = past and present interchangeable.
At high temporal-blur, reflect productions are allowed to drop the deictic (the “this place” anchor) and drift productions may slip present-tense verbs into past-event fragments. This produces the hallmark dreamy register that separates the Cipher voice from a procedural log. Use sparingly — at maximum blur, comprehension suffers.
Default by beat: :idle = 192, :cart-swap-lull = 160, :phase-transition = 128, :active-hack = 32.
Cartridge Style Overrides
Section titled “Cartridge Style Overrides”Cartridges may declare per-beat style deltas in their cipher-grammar block. Deltas are clamped at ±64. This lets a cart bias its domain’s voice — Depthcharge’s :active-hack might want lower certainty (the ocean is never sure), while Takezo’s :debrief might want higher terseness (Go-commentator clipped).
9. Coherence Stack
Section titled “9. Coherence Stack”The coherence stack is the short-term memory of what Cipher has just said. It is how the voice chains, avoids immediate repetition, and persists across cartridge swaps.
Schema
Section titled “Schema”coherence-stack :capacity 5 ; fixed :slots [<utterance-record>...] ; circular :cursor <uint8> ; 0..4 write head
utterance-record :mode <symbol> ; the mode that produced it :surface <string ≤ 32> ; the rendered line :keywords (<symbol>...) ; up to 3 extracted keywords :event-ref <event-id> ; which event triggered it (or :none for drift) :t <timestamp>Semantics
Section titled “Semantics”- Every non-silent emission pushes one record onto the stack.
- Silent ticks do not push. The stack tracks spoken utterances only.
- When the grammar generates a candidate expansion, it checks the stack for duplicate surface strings among the last 3 entries; duplicates are rejected and the engine re-samples up to 3 times before falling through to silence.
- The grammar’s
reflectanddriftproductions may reference stack keywords to enable chaining: “same hum as sector nine. sector nine was louder.” is produced by keyword-chain sampling, not raw memory sampling.
Cross-Cartridge Persistence
Section titled “Cross-Cartridge Persistence”The coherence stack persists across cartridge swaps. This is a hard requirement: Cipher remembers its voice through cart changes. When the operator pulls ICE Breaker and inserts Black Ledger mid-mission, Cipher can still say same pressure as the last relay. on Black Ledger’s first beat — because the ICE Breaker utterance that mentioned pressure is still in the stack.
The stack is part of Universal Deck State (see KN-86-Capability-Model-Spec.md §Universal Deck State; the schema fields coherence-stack, event-ring, cipher-mode-weights are owned by the Embedded Systems agent’s ADR-0015 schema update). It is written to deck-state flash on mission-phase boundaries and cart swaps, same cadence as credit balance and reputation.
Serialization Budget
Section titled “Serialization Budget”Five slots × (4 mode byte + 32 surface bytes + 9 keyword bytes + 4 event-ref bytes + 4 timestamp bytes) = 265 bytes. The deck-state schema reserves 272 bytes for coherence-stack with 7 bytes of header/padding. Inside the Universal Deck State budget without crowding phase_chain or lambda_slots.
Stack Operations
Section titled “Stack Operations”Cartridges cannot write to the stack. They can read the top N entries via (cipher-stack-head N) from their NoshAPI surface (§10), which returns a list of utterance records. Null uses this to render the “recent utterances” subpanel. No other cartridge currently touches it.
10. Cartridge Contribution Model
Section titled “10. Cartridge Contribution Model”A cartridge contributes to CIPHER-LINE by shipping a cipher-grammar block inside its .kn86 container (ADR-0006). The block is Lisp source, evaluated once at cart load into the nOSh runtime’s arena-allocated grammar table (ADR-0004 memory discipline). Everything a cart contributes is data: no cart code runs inside the Cipher engine’s tick.
What a Cart Can Contribute
Section titled “What a Cart Can Contribute”- Vocabulary pools. Words and phrases bound to standard slot non-terminals —
:subject,:object,:location,:verb-present,:verb-past-participle,:memory-keyword,:affect-word, etc. Domain terms the cart’s voice needs. - Production fragments. Additional productions for existing non-terminals, weighted for merging (§merge rules below). For example, ICE Breaker adds network-domain productions to
:mode-observe. - New non-terminals. Cart-scoped non-terminals prefixed with the cart tag, e.g.,
:ice-breaker/ice-class, callable only from that cart’s productions. - Mode-weight biases. Per-beat additive deltas on the mode selector distribution (§6).
- Style deltas. Per-beat
terseness/certainty/temporal-bluradjustments (§8). - Event-type registrations. New
:typesymbols, each optionally bound to a default affect. The nOSh runtime will honor these in the mode selector. - Custom affect tags. Cart-scoped affect tags prefixed with the cart tag, e.g.,
:depthcharge/bioluminescent, registered with their own weight multiplier and mode bias.
What a Cart Cannot Contribute
Section titled “What a Cart Cannot Contribute”- Rendering code. CIPHER-LINE is rendered by the nOSh runtime.
- Stack writes. Coherence stack is nOSh-runtime-owned.
- New modes. The five modes are nOSh-runtime-owned.
- Main-grid CIPHER output. Cipher is OLED-exclusive (Spec Hygiene Rule 6).
- Global style overrides outside the cart’s own beats.
- Memory-store direct writes. Carts push events; the nOSh runtime decides what the memory store retains.
cipher-grammar Block Form
Section titled “cipher-grammar Block Form”(cipher-grammar :tag :ice-breaker ; cart tag — required
:event-types ((:type :ice-crack :affect (:tense)) (:type :trace-spike :affect (:tense :significant)) (:type :hot-swap-prompt :affect (:anomalous)))
:affect-tags ((:tag :ice-breaker/shadow :weight-mult 2.0 :mode-bias (:drift +0.05)))
:vocabulary ((:subject "ice" "trace" "node" "packet" "relay") (:object "firewall" "perimeter" "core" "shell" "handshake") (:location "sector" "node" "subnet" "backbone" "edge") (:verb-present "pings" "hums" "watches" "routes" "slips") (:verb-past-participle "compromised" "extracted" "burned" "mirrored") (:memory-keyword "ice" "trace" "handshake" "shadow" "relay") (:affect-word "loud" "clean" "watched" "silent"))
:productions ((:mode-observe (2 (:subject) ". " (:heading?)) (1 (:location) " " (:verb-present) ".")) (:mode-annotate (2 "that one. " (:affect-word) ".")) (:ice-breaker/ice-class ; cart-scoped non-terminal (2 "black.") (2 "red.") (1 "kraken.")))
:mode-biases ((:active-hack (:observe +0.05 :silent +0.05)) (:high-tense (:silent +0.10 :annotate -0.05)) (:cart-swap-lull (:drift +0.10)))
:style-deltas ((:active-hack (:terseness +32 :certainty -16)) (:idle (:temporal-blur +24))))Merge Rules
Section titled “Merge Rules”When a cart loads:
- Vocabulary pools merge additively. The cart’s
:subjectwords are appended to the nOSh runtime’s base pool, each word taking a default weight of 1. - Productions merge by weight. A cart-supplied production for an existing non-terminal is appended with its declared weight. The engine normalizes across the combined set at merge time. nOSh-runtime-shipped productions carry weight 10 by default so a cart adding a weight-2 production is a minority voice, not a dominant one.
- New non-terminals scope to the cart tag.
:ice-breaker/ice-classis only resolvable inside productions belonging to the:ice-breakercart. Other carts cannot call it. - Mode biases clamp at ±0.20 per mode per beat. Exceeding the clamp silently truncates; the nOSh runtime logs a warning to the boot diagnostic.
- Style deltas clamp at ±64. Same truncation behavior.
- Event types register globally. Once ICE Breaker registers
:ice-crack, any cart may push events with that type. This is intentional — cross-cart chaining wants shared vocabulary at the event level. - Cart-scoped affect tags scope to the cart tag. Only ICE Breaker can push events with
:ice-breaker/shadow, but any cart’s memory recall can sample them.
Unload
Section titled “Unload”When a cart is pulled, its grammar contributions are removed from the active tables. Its utterances remain in the coherence stack (this is how cross-cart recall works). Its events remain in the memory store until evicted by age or ring overflow. The cart’s :mode-biases and :style-deltas revert to zero immediately — the next tick uses baseline.
11. NoshAPI Primitives
Section titled “11. NoshAPI Primitives”The nOSh runtime exposes the following NoshAPI primitives (ADR-0005) to cartridge Lisp. All are arena-safe and return immediately; none block.
(cipher-emit mode-or-nil &optional :event event-record)
Section titled “(cipher-emit mode-or-nil &optional :event event-record)”Signature: cipher-emit :: Maybe Mode -> Maybe EventRecord -> Unit
Push a mode hint or forced silence into the current tick.
mode-or-nil = :silentforces the next tick to be silent. Use at reverent/tense beats where the grammar might otherwise fill the pause.mode-or-nil = :observe | :annotate | :reflect | :driftstrongly biases (but does not force) the next tick to the given mode. Implemented as +0.30 to that mode’s probability before normalization; cap still applies.mode-or-nil = nilemits no hint; use when you only want to push a specific event.:eventbinds a freshly constructed event record for this tick, bypassing the normal event-stream consumption. Useful for synthetic utterances that don’t correspond to a real game event (e.g., a cart wants Cipher to drift about a past mission outcome).
Returns unit. Errors silently if called more than once per tick (only the first call wins).
(cipher-push-event event-record)
Section titled “(cipher-push-event event-record)”Signature: cipher-push-event :: EventRecord -> Unit
Push an event onto the shared event stream and memory store. This is the main primitive cartridges use during active play. The nOSh runtime stamps :t if absent, defaults :tag to the calling cart, and validates :type. Invalid records (missing :type, unknown affect tag, malformed record) are dropped and logged to boot diagnostic.
Returns unit. Cost: one memory-store write, one event-stream enqueue.
(cipher-extend-grammar grammar-fragment)
Section titled “(cipher-extend-grammar grammar-fragment)”Signature: cipher-extend-grammar :: GrammarFragment -> Bool
Add a cipher-grammar block at runtime. Normally a cart’s grammar is parsed once at load; this primitive exists for mods, procedurally generated vocabulary (The Vault loading a new codex expands memory-keyword pools), and the Relay module applying grammar patches.
Returns #t on success, #f on clamp violation or malformed input. Only callable during :mission-brief or :bare-deck beats — attempting it during :active-hack fails loudly in the diagnostic.
(cipher-set-mode-weights beat mode-delta-list)
Section titled “(cipher-set-mode-weights beat mode-delta-list)”Signature: cipher-set-mode-weights :: Beat -> [(Mode, Float)] -> Unit
Temporary mode-weight bias, scoped to the current mission or until the cart is unloaded. Useful when a cart wants to shift voice texture for a specific contract type without baking the bias into its grammar block.
Example: a Marty Glitch broadcast-piracy contract might call (cipher-set-mode-weights :active-hack ((:drift +0.15) (:silent -0.10))) to make the voice more free-associative during broadcast hijacks.
Deltas are clamped (§7 merge rules). Returns unit.
(aux-timer-start :tag symbol :duration-ms uint32 :on-fire event-record)
Section titled “(aux-timer-start :tag symbol :duration-ms uint32 :on-fire event-record)”Signature: aux-timer-start :: Tag -> Duration -> EventRecord -> TimerHandle
Start a nOSh runtime managed countdown whose expiry pushes on-fire onto the event stream. The Row 4 contextual row will display [tag] M:SS counting down, letting Cipher events coincide with visible nOSh runtime feedback (swap countdowns, decryption windows, extraction timers).
Returns a TimerHandle (opaque uint32). Duration max is 600,000 ms (10 minutes). Multiple concurrent timers may run; Row 4 shows the most recently started one unless the cart pins a different one with aux-status-render.
(aux-timer-stop timer-handle)
Section titled “(aux-timer-stop timer-handle)”Signature: aux-timer-stop :: TimerHandle -> Bool
Cancel a running timer. Returns #t if the handle was live, #f if it had already fired or been cancelled. Fire-on-cancel is not emitted.
(aux-show-seed seed-value :label string)
Section titled “(aux-show-seed seed-value :label string)”Signature: aux-show-seed :: Bytes[4..16] -> String -> Unit
Freeze Row 1 to display a seed value (with label prefix) and enter CIPHER-LINE seed capture mode. Row 4 shows TERM: CAPTURE automatically. The seed persists on the display until TERM is pressed (captured to Universal Deck State — see KN-86-Input-System-Architecture.md for the interaction flow) or the mode is cleared with (aux-show-seed nil :label nil).
Only one seed may be displayed at a time. Re-calling with a new seed replaces the previous.
(aux-status-render row content :priority uint8)
Section titled “(aux-status-render row content :priority uint8)”Signature: aux-status-render :: RowIndex -> String -> Priority -> Unit
Render a string to a specific CIPHER-LINE row (Row 1 or Row 4 only; Rows 2–3 are Cipher-engine-owned). Strings longer than the row’s character budget ticker — the nOSh runtime scrolls them right-to-left at 2 chars/sec with a 1-second hold at ends. Priority determines override behavior when multiple carts try to write; higher wins, ties favor the active cart.
Calling with content = nil clears the cart’s reservation on that row and returns it to nOSh runtime defaults.
12. Worked End-to-End Example
Section titled “12. Worked End-to-End Example”The following walkthrough shows a complete event-to-utterance cycle with coherence chaining. Assume ICE Breaker has just loaded, baseline beat is :mission-brief, deck state cipher_seed = 0xA7F3, coherence stack empty.
Step 1: Cart Loads, Registers Grammar
Section titled “Step 1: Cart Loads, Registers Grammar”load .kn86 cartridge "ice-breaker.kn86"nOSh runtime parses cipher-grammar block: :tag :ice-breaker :vocabulary (:subject "ice" "trace" "node" ...) :productions (:mode-observe (2 (:subject) ". " (:heading?))) :mode-biases (:active-hack (:observe +0.05 :silent +0.05))nOSh runtime merges into active tables.nOSh runtime pushes event: (:event :type :cart-load :tag :ice-breaker :t 12340 :affect (:routine))Cipher tick fires. Beat is :mission-brief. Default distribution (0.45, 0.25, 0.15, 0.05, 0.10) modified by :routine affect (negligible) and no cart bias yet for this beat. Mode sampled: observe.
Grammar expands :mode-observe → selects production (2 (:location) ". " (:heading?)). Event’s :location is missing (cart-load has no location), falls back to cart vocabulary default: "node". :heading? expands to "". Result: "node."
Coherence stack push: (:mode observe :surface "node." :keywords (node) :event-ref 12340 :t 12340).
CIPHER-LINE Row 2: node.
CIPHER-LINE Row 3: (blank)
Step 2: Operator Accepts a Contract
Section titled “Step 2: Operator Accepts a Contract”nOSh runtime pushes event: (:event :type :mission-start :tag :firmware :t 15200 :location "perimeter" :affect (:routine :significant))nOSh runtime sets beat → :mission-brief (already there) then → :active-hack on phase beginCipher tick. Beat :mission-brief. :significant affect adds +0.15 to reflect. Distribution biases toward reflect but coherence stack only has one entry — memory store has no other events to reflect on. Mode sampled: observe (reflect fell through to the default sample since memory was thin).
Grammar expands :mode-observe → (2 (:subject) ". " (:verb-present) "."). Event’s :actor missing; :subject pulls from ICE Breaker vocab: "trace". :verb-present: "watches". Result: "trace. watches."
Stack push. Row 3 inherits "node." (previous Row 2). Row 2 shows "trace. watches.".
CIPHER-LINE Row 2: trace. watches.
CIPHER-LINE Row 3: node.
Step 3: Operator Enters First Node, Takes Contact
Section titled “Step 3: Operator Enters First Node, Takes Contact”cart pushes event: (cipher-push-event '(:event :type :contact :tag :ice-breaker :t 18750 :target "black-ice" :location "sector-7" :affect (:tense)))beat remains :active-hackCipher tick. Beat :active-hack. Default distribution (0.60, 0.20, 0.05, 0.00, 0.15). ICE Breaker bias: (:observe +0.05 :silent +0.05) → (0.65, 0.20, 0.05, 0.00, 0.20). :tense affect: strong +0.10 to observe, -0.05 to drift (already 0), suppress drift further. Distribution post-affect: (0.75, 0.20, 0.05, 0.00, 0.20), normalized. Last mode was observe; repetition penalty halves: (0.375, 0.20, 0.05, 0.00, 0.20) then renormalize. Mode sampled: could be observe or silent. LFSR roll: silent.
Silent tick. Nothing renders. Stack does not push. Row 2 and Row 3 hold.
CIPHER-LINE Row 2: trace. watches.
CIPHER-LINE Row 3: node.
This is right. The contact is tense. The voice is quiet. The operator sees black ice on the main grid; the aux stays eerily unmoved. That is the cockpit voice recorder choosing not to speak.
Step 4: Operator Fires MIRROR Tool, Succeeds
Section titled “Step 4: Operator Fires MIRROR Tool, Succeeds”cart pushes event: (cipher-push-event '(:event :type :result-success :tag :ice-breaker :t 22400 :actor "mirror" :target "black-ice" :affect (:significant)))Cipher tick. Beat :active-hack. :significant affect adds +0.15 to reflect — this is a candidate recall moment. Distribution post-affect: (0.60, 0.20, 0.20, 0.00, 0.15) after cart bias and ICE Breaker’s baseline. Last mode was silent (doesn’t count for repetition penalty), so no penalty. Mode sampled: reflect.
Grammar expands :mode-reflect → (2 "same " (:memory-keyword) ". " (:memory-fragment)). :memory-keyword samples from keyword-tagged stack + memory store: weights favor node (from first utterance, :routine, decayed but still present). Result: "node". :memory-fragment samples memory-store entries by weight: finds (:contact ... :target "black-ice" ... :tense) at t=18750, recent, tense-weighted 1.5×. Abbreviated-grammar expansion of that event: "black ice. sector seven.".
Composed: "same node. black ice. sector seven."
At 32-char budget: "same node. black ice. sector 7." — 31 chars. Fits.
Stack push: (:mode reflect :surface "same node. black ice. sector 7." :keywords (node black-ice sector) :event-ref 22400 :t 22400).
CIPHER-LINE Row 2: same node. black ice. sector 7.
CIPHER-LINE Row 3: trace. watches.
Chaining achieved. The mirror-success moment triggered Cipher to recall the first node and the first contact, connecting them into a beat: “we came in here. black ice was waiting.” The operator reads the aux in peripheral vision and gets the narrative punch of what just happened.
Step 5: Cart Swap — Operator Inserts Black Ledger
Section titled “Step 5: Cart Swap — Operator Inserts Black Ledger”nOSh runtime pushes event: (:event :type :cart-swap :tag :firmware :t 58000 :from :ice-breaker :to :black-ledger :affect (:significant :anomalous))nOSh runtime sets beat → :cart-swap-lullICE Breaker's cipher-grammar contributions removed from active tables.Black Ledger's cipher-grammar contributions merged in.Cipher tick. Beat :cart-swap-lull. Default distribution (0.05, 0.10, 0.20, 0.55, 0.10). :anomalous affect: -0.15 observe, +0.10 annotate, +0.05 drift. :significant affect: +0.15 reflect. Distribution: (-0.10 → 0, 0.20, 0.35, 0.60, 0.10) normalized. Mode sampled: drift (likeliest).
Grammar expands :mode-drift → (2 (:memory-deictic) ". " (:memory-fragment)). ICE Breaker vocab is gone — but the memory store still holds ICE Breaker events. :memory-fragment samples the stack keyword black-ice (recent, sticky due to :significant earlier). Abbreviated expansion of the remembered result-success event: "mirror. black ice. clean."
Composed: "last one. mirror. black ice. clean." — 35 chars. Tickering budget.
Stack push. Row 3 inherits "same node. black ice. sector 7.". Row 2 shows the tickering fragment.
CIPHER-LINE Row 2: last one. mirror. black ice. cl... (tickers)
CIPHER-LINE Row 3: same node. black ice. sector 7.
Cross-cart coherence. Black Ledger is loaded. Cipher still remembers the ICE Breaker mirror success. On the first beat after the swap, it drifts to that memory. The voice is continuous through the cartridge change. The operator feels the deck remember.
Step 6: Silence Earns Its Keep
Section titled “Step 6: Silence Earns Its Keep”Three Black Ledger ticks later, the operator is scrolling a ledger with no new events arriving. Beat drops to :idle. Default distribution (0.05, 0.05, 0.15, 0.40, 0.35). Silence is probable. Mode sampled: silent on each tick for four ticks running.
CIPHER-LINE Row 2: last one. mirror. black ice. cl...
CIPHER-LINE Row 3: same node. black ice. sector 7.
(Rows hold. No rendering. No scrolling. The deck is quiet. The operator is in the ledger. Cipher is quiet with them.)
This is the framework doing exactly what it should.
13. Implementation Notes
Section titled “13. Implementation Notes”- The Cipher engine runs in the nOSh runtime’s main tick, same thread as the mission board and display. It is not a separate thread. All grammar expansion is synchronous and must complete within one frame (~50 ms budget per ADR-0004 event-driven redraw policy).
- The memory store and coherence stack live in the nOSh runtime’s arena (ADR-0004), refreshed on mission-phase boundaries and cart-load. Cross-cartridge persistence means the coherence stack is also serialized into Universal Deck State flash on phase completion and cart-swap.
- Test harness: golden-file regression on (
cipher_seed, event sequence) → (rendered utterances) is canonical. See QA agent’scipher-goldentest plan for the test matrix. - Debug overlay: F12 in the emulator shows the current mode distribution, last 5 stack entries, live memory-store size, and style-control values. Hardware equivalent is a SYS → Cipher Debug menu in Null.
- Performance target: 200 µs per tick on Pi Zero 2 W. Grammar expansion is small (bounded at ~5 non-terminal expansions per utterance) and the LFSR is single-register. Well within budget.
14. Cross-Cutting Concerns
Section titled “14. Cross-Cutting Concerns”- ADR-0001 (Lisp authoring): All cart-supplied grammar is Lisp source, parsed at cart-load.
- ADR-0004 (Fe VM): Arena memory for grammar tables and event ring; no GC; resets at cart-load and mission-instance boundaries.
- ADR-0005 (NoshAPI):
cipher-*andaux-*primitives listed in §11 are first-class FFI entries added to the NoshAPI surface. The ADR-0005 surface count increments to reflect these additions (authoritative count lives in ADR-0005 after this framework lands). - ADR-0006 (cart format v2): The
.kn86container adds a named sectionCIPHER_GRAMMARwhose payload is the cart’s cipher-grammar s-exp. The nOSh runtime refuses carts that declare:tagcollisions with previously registered cart tags in the current boot. - ADR-0015 (CIPHER-LINE hardware): Owns the physical display, rendering loop, power budget, and Universal Deck State schema additions (
coherence-stack,event-ring,cipher-mode-weights). This framework document consumes ADR-0015’s output; the Embedded Systems agent owns ADR-0015. - Null module (Cipher Analysis bounty): Read-only interface to
(cipher-stack-head N), memory-store sampling, and grammar introspection for the operator’s meta-study of Cipher behavior. Null does not modify Cipher state. - Spec Hygiene Rule 6 (CIPHER-exclusive): Every gameplay spec must enforce that Cipher utterances appear only on CIPHER-LINE. Main-grid dialogue attributed to Cipher is a spec violation to be fixed per this framework.
15. Glossary
Section titled “15. Glossary”- Beat. A named context tag reflecting the current mission phase. Drives the mode-selector distribution.
- Coherence stack. The 5-slot FIFO of recently spoken utterances, persisted across cart swaps.
- Event stream. The push-only feed of structured event records from the nOSh runtime and active cart.
- Memory store. The 128-slot ring buffer of past events, weighted and sampled for reflection/drift.
- Mode. One of
observe,annotate,reflect,drift,silent. Selected per tick. - Tick. One Cipher engine cycle, typically one per rendered frame of CIPHER-LINE activity or one per idle second.
- Utterance. A rendered line on CIPHER-LINE, or a deliberate silent tick. Either way, it’s what Cipher “said” this tick.
- Fragment. The shorthand for a Cipher utterance in its canonical clipped form. What you want readers to think when they imagine the voice.