Skip to content

Design: CIPHER-LINE Modes, Boot Splash, and the oled/ Library

chosen by Josh from mocked options). Implementation queued; see §10. Task: GWP-727. Related: ADR-0015 (CIPHER-LINE surface + FFI), ADR-0017 (coprocessor), coprocessor-protocol.md, 2026-07-02-oled-8080-bench-bringup.md (deleted 2026-07-29; recover from git history) (GWP-619), Canonical Hardware Specification (authoritative values). Mocks: design/mockups/cipher-line/: pixel-exact, rendered with the runtime’s kn86_font at true 256×64 geometry by a pure KEC Lisp generator (gen-mocks.lsp, GIF89a/LZW encoder included, writing each .gif directly via binary-safe blob writes; run with kec run …/gen-mocks.lsp).


Josh reviewed the bench OLED (2026-07-18 photo) and called out four gaps:

  1. No start-up image or animation.
  2. The running layout on the bench glass has overlapping elements and odd spacing.
  3. No display modes (first wanted: a full-screen HH:MM:SS clock that updates).
  4. No Lisp library for managing the aux display.

Diagnosis of (2): the overlap is an artifact of the GWP-619 scratch bench shell, whose TEXT / BIG / RECT / BARS primitives draw independently with no notion of fields or ownership, so bars land on top of labels and text crosses the column rule. The scratch shell is bench-only and is not in the tree. The durable fix is the layout model in this design: on the product path a single owner composes the panel, and row content is placed through field helpers that clear their span before drawing. When the product wire path replaces the scratch shell, the failure mode is gone structurally.

Current runtime state (for orientation): the panel is written from C only: cipher.c (voice rows), oled_row4.c (Row 4 claim stack), nemacs.c, and peers, over oled.c’s four 32-char logical rows. Carts reach it through the ADR-0015 cipher-* / aux-* NoshAPI primitives. There is no Lisp surface, no pixel-level render tier, and no full-screen concept.

Three options were mocked for each open call; Josh chose:

QuestionChoice
Boot splashOption A: wordmark type-on (splash_a_typeon.gif). Runs entirely over row-text primitives, so it ships on the current wire commands; options B (phosphor sweep) and C (POST bar) not chosen, mocks retained for reference.
Device render path for big glyphs / full-screen modesFrame blit. The Pi renders into the existing 2 KB oled.c framebuffer and ships whole frames over UART (~20 ms per frame at 1 Mbps, comfortable for 10–20 fps cadences). The Pico blits. Row commands stay as low-bandwidth fast paths.
Mode entryLisp call first. (oled/mode :clock) from the REPL or any system surface now; a SYS-layer key chord is a small follow-up once modes exist.

The panel has exactly one owner at a time:

  • Default owner: ROWS. The ADR-0015 four-row layout, written by the existing C writers. Unchanged behavior when no mode is active.
  • Full-screen modes claim the whole panel. While a mode is active, a C-side gate in oled.c suspends row-oriented writes; the writers keep updating their own state (cipher scrollback, Row 4 claim stack) so nothing is lost. On mode exit the panel redraws the rows from current state.

The mode stack lives in Lisp (oled/, §5); the gate lives in C (oled.c, §4). This mirrors the split used elsewhere in the runtime: C owns the buffer and the invariant, Lisp owns the policy.

The splash is itself a mode (:boot), pushed at nOSh start and popped when its sequence completes. The clock is the second mode. Future modes (VU meter, link status, timers) are additional oled/define-mode registrations with no new C work.

A small pixel tier beside the existing row tier, same static-buffer discipline:

  • oled_big_text(oled, x, y, scale, text): kn86_font glyphs at an integer scale. At scale 4 a glyph is 32 px, so HH:MM:SS (8 glyphs) spans the full 256 px width.
  • oled_fill_rect(oled, x, y, w, h, on): set or clear a pixel rectangle (rules, bars, cursor blocks).
  • Mode gate: oled_mode_claim(owner) / oled_mode_release() plus a check in the row-write entry points. Row writes while claimed are dropped at the panel (writer state is unaffected); release triggers a full row redraw from current state.

5. The oled/ library (runtime/system-image/lib/oled/oled.lsp)

Section titled “5. The oled/ library (runtime/system-image/lib/oled/oled.lsp)”

System-tier Fe module. Public surface (namespaced oled/…, KEC Core vocabulary):

  • Modes: (oled/define-mode name plist) with :enter / :render / :tick / :exit callbacks and a :tick-hz rate; (oled/mode name) pushes, (oled/pop) restores the previous owner. Ticks ride the GWP-644 idle-timer library (run-with-timer), armed lazily and guarded like every other animated surface.
  • Fields (the layout discipline): (oled/field row col width text) clears its span then draws, truncating at the field edge; (oled/right row text) right-aligns. The Row 1 status strip is recomposed from fixed fields (battery / timer / mode chip / TERM hint, per the ADR-0015 row table); status_mode.gif is the target render. Fields make the bench-glass overlap unrepresentable: content cannot escape its span.
  • Built-in modes: :boot (§6) and :clock (§7).

New system-tier bridge primitives back the library: oled-set-row, oled-big-text, oled-fill-rect, oled-clear, oled-mode-claim, oled-mode-release. These bind only in the System KEC context, exactly like the render/* tier; loading them from a cart context raises unbound-symbol. The cart-facing NoshAPI surface (ADR-0005/0015 cipher-* / aux-*) is unchanged. Carts still cannot address the panel directly, so no ADR amendment is required.

Storyboard, per splash_a_typeon.gif (all row/fill primitives, works on today’s wire commands):

BeatContentTiming
1KINOSHITA types on glyph-by-glyph at 2× with a trailing block cursor, centered upper panel90 ms/glyph
2KN-86 // CIPHER-LINE ONLINE types on at 1× below, 6 glyphs/frame~70 ms/frame
3A 2 px rule sweeps left to right under the subline~240 ms
4Hold~1 s
5Cut to the running four-row layout

Runs at nOSh host init on both targets (emulator and device) since it sits above the backend seam. A Pico-resident static splash for the power-on window (the panel is otherwise dark until the Pi boots and nOSh connects) is product-firmware scope and is deferred with the rest of pico2-firmware/ per the GWP-152 gating rule; the design intent is recorded here so the firmware task inherits it.

(oled/mode :clock): 24-hour HH:MM:SS at scale 4 (full 256 px width, 32 px tall, vertically centered), redrawn on a 1 Hz tick. Mock: clock_mode.gif. Time source is CLOCK_REALTIME, which on the device is RTC-backed per the offline-first timekeeping direction; the emulator uses the host clock. (oled/pop) returns to the rows.

One new command, frame blit: the full 2048-byte 1-bpp framebuffer, with the transaction/ACK boundary the GWP-619 bench note already recommends for smooth animation. Spec lands in coprocessor-protocol.md; the runtime dispatches it through the existing coproc vtable (in-process no-op path for the emulator). The Pico-side implementation belongs to the existing Pico 2 firmware Phase 3 (SSD1322 driver) task. Existing OLED_SET_ROW / SCROLL / FILL / CLEAR commands remain the low-bandwidth path for ordinary row updates. This design does not touch the SPI-vs-8080 panel-bus question; ADR-0015 §F8 owns that reconciliation, and the wire protocol is independent of the panel bus.

  • Headless ctest: pixel tier (big-text geometry, fill-rect bounds), mode gate (no bleed-through into or out of a claimed mode), System-context-only binding of the bridge primitives.
  • Every implementation story ends with a kn86rec real-screen recording (splash play, clock ticking, status strip fields).
TaskScope
GWP-727This design (doc + mocks + PR).
Add pixel tier and full-screen mode gate to oled.c§4.
Build the oled/ system-tier Fe library with mode manager and clock mode§5 + §7.
Implement CIPHER-LINE boot splash sequence (wordmark type-on)§6.
Add OLED frame-blit command to the coprocessor protocol§8.