KN-86 ASCII Effect Spec
⚠ PROMOTED — partial. As of 2026-06-07 (ADR-0033), the
(reveal …)portion of this spec has been promoted to canonical atdocs/software/api-reference/grammars/reveal-styles.md. Cart authors and runtime engineers should read the canonical doc — not this one — for the(reveal …)primitive contract, style enum, lifecycle, parameter ranges, and Pi Zero 2 W performance budget. The(reveal …)discussion below (§“Interaction grammar” item 3 and §“NoshAPI surface”reveal/unrevealsnippets) is preserved for design-history narrative only.The remaining surfaces in this doc — the
:hover/:clickinteraction grammars,(draw-ascii …), project-wide defaults like the 72-char ramp andcharMode = shape— remain inspiration-tier with no canonical promotion. A future ADR may promote them if a real v0.1 caller surfaces. For now: read this doc as future-scope reference, not as a binding contract.
- Source: distilled from react-video-ascii and libcaca, Batch 6
- Category:
effect— interaction-grammar spec for ASCII rendering across KN-86 surfaces - Role for KN-86: the single project-wide grammar for how KN-86 renders imagery + reacts to hover, click, and load events. The whole TUI shares one ASCII visual language defined here. (The
(reveal …)portion is now canonical atreveal-styles.mdper ADR-0033; the rest remains inspiration-tier.) - License / caveats: this is KN-86’s own spec, distilling props from MIT-friendly published projects. Independent implementation; no vendored code.
Purpose
Section titled “Purpose”Batch 6 introduces ASCII rendering as a first-class KN-86 capability — converting raster imagery (splash art, cart preview thumbnails, album-cover-equivalents, boot art) and interactive surfaces into colour ASCII / Unicode text. This spec defines the project-wide defaults and interaction grammar so every cart, every surface, every animation hand-shakes against the same vocabulary.
Two batches of prior art compose into this spec:
- react-video-ascii is the interaction-grammar north star. Its props (numColsRaw, charMode, mouseEffect, clickEffect, revealEffect) define the operations and tunables KN-86 should expose to cart authors through NoshAPI.
- libcaca is the engine north star. Its image→text pipeline (
img2txt, charset palettes, dithering modes) defines how KN-86’s runtime actually rasterises an image down to amber cells. - asciinator (Batch 3) is the technique foundation — halfblock-
▀with fg+bg color, three rendering modes (halfblock, shade, ascii), 24-bit-color emit format.
Defaults — project-wide
Section titled “Defaults — project-wide”These are the canonical KN-86 defaults. A cart that doesn’t override these uses these.
| Setting | KN-86 default | Range | Notes |
|---|---|---|---|
numCols | 150 | 20–350 | Columns of ASCII output. 150 is the react-video-ascii midpoint and reads cleanly at the KN-86 80×25 grid scale when the imagery occupies a sub-region of the cart-content area. Carts rendering full-width imagery use 80 (the canonical column count). |
charMode | shape | shape | luminance | shape matches glyph silhouettes to image features; luminance uses brightness only. KN-86 commits to shape as the default because it preserves edge detail at amber-on-black where luminance-only loses high-frequency content. |
chars | standard ramp (see below) | — | Verbatim from react-video-ascii. |
brightness | 1.0 | 0.0–2.0 | Multiplier. The aesthetic-mode AMBER palette (Batch 2 visual identity brief) sets the absolute intensity; this is the per-render boost. |
saturation | 0.0 | 0.0–2.0 | KN-86 is monochrome amber; saturation is structurally zero. Carts that ship in a multi-color emulator preview can override; on-device renders ignore. |
bgOpacity | 0.3 | 0.0–1.0 | Background-cell intensity below the foreground glyph. Low bgOpacity keeps the device looking like text-on-black; higher bgOpacity reads more like image-with-text-overlay. |
cropFocus | center | left | center | right | Where to anchor cropping when the source image’s aspect doesn’t match the render target’s aspect. |
Default character ramp
Section titled “Default character ramp”The canonical KN-86 character ramp, dark to bright (verbatim from react-video-ascii):
`.',-_:!;|\"~+^lr`\/L`>t<v=Tz?icf1{sIxY*jJno}CZyVwmSXRqM$O%#9&NW0Q@Length: 72 characters. Includes punctuation (low density), brackets and slashes (mid density), upper-case letters and symbols (high density), terminating in @. The ramp is the KN-86 project default for ASCII rendering of static imagery in shape mode; cart authors who want a different ramp pass their own.
Cart-author alternative ramps to know about
Section titled “Cart-author alternative ramps to know about”Three sub-ramps worth documenting as named alternatives (not v0.1 defaults, but cited for cart authors):
tight-72— the default aboveclassic-9— the historical short ramp@%#*+=-:.`— 9 characters; reads chunky, deliberately period-correctascii-only— strip the default ramp to pure 7-bit ASCII (drop\\,^,~) for terminals that don’t render extended characters consistently
Interaction grammar — three event-types
Section titled “Interaction grammar — three event-types”Borrowed directly from react-video-ascii’s props matrix; each becomes a NoshAPI primitive cart authors can attach to any ASCII surface.
1. Hover / mouse effect — (:hover style :brighten :options {...})
Section titled “1. Hover / mouse effect — (:hover style :brighten :options {...})”KN-86 doesn’t have a mouse, so “hover” translates to keyboard-cursor position over an ASCII surface. The character cell the deck’s focus indicator currently overlaps is the “hovered” cell.
Two styles, both inherited from react-video-ascii:
brighten (default)
Section titled “brighten (default)”Hovered cell + a trailing wake brighten relative to baseline.
| Param | KN-86 default | Range | Notes |
|---|---|---|---|
trailLen | 15 | 0–30 | Cells tracked in the trail behind the focus cursor. Higher = more lingering wake. |
brightness | 2.0 | 0.2–5.0 | Brightness multiplier at the cursor cell, fading to 1.0 across the trail. |
trailDecay | 0.85 | 0.0–1.0 | Per-step exponential decay of brightness along the trail. |
radius | 0.08 | 0.03–0.2 | Effect radius as a fraction of the smaller canvas dimension. |
duration | 1.0 | 0.1–4.0 | Seconds the effect lingers after focus leaves the cell. |
scatter (alternative)
Section titled “scatter (alternative)”Hovered region’s characters are replaced with chars from a scatter set (e.g., '->o').
| Param | KN-86 default | Range | Notes |
|---|---|---|---|
scatterChars | '->o' | — | Substituted character set. |
radius | 0.05 | 0.03–0.2 | Same shape as brighten radius. |
duration | 0.5 | 0.1–4.0 | Seconds the scatter lingers. |
2. Click / commit effect — (:click style :ripple :options {...})
Section titled “2. Click / commit effect — (:click style :ripple :options {...})”KN-86’s “click” translates to a commit keystroke on the focus cell — typically Enter, the link-hint label key, or a verb dispatch. Two styles:
ripple (default)
Section titled “ripple (default)”A brightness ring radiates outward from the committed cell.
| Param | KN-86 default | Range | Notes |
|---|---|---|---|
speed | 3.0 | 0.5–10.0 | Cells-per-second the ring expands. |
brightness | 1.1 | 1.05–2.0 | Brightness boost of the ring relative to baseline. |
duration | 1.0 | 0.1–4.0 | Total seconds before the ring fades to baseline. |
spread (alternative)
Section titled “spread (alternative)”A scatter region expands outward from the committed cell.
| Param | KN-86 default | Range | Notes |
|---|---|---|---|
spreadSpeed | 7.5 | 0.5–10.0 | Speed of the spread wave front. |
spreadExpandDuration | 1.5 | 0.5–5.0 | Seconds for the region to fully expand. |
3. Reveal effect on load — (:reveal style :random :options {...})
Section titled “3. Reveal effect on load — (:reveal style :random :options {...})”When an ASCII surface first appears, the characters reveal in sequence rather than appearing all at once. Three patterns:
| Style | When to use |
|---|---|
diagonal | Top-left to bottom-right wipe. Reads as deliberate, structural. Use for committed/important surfaces (mission accept screen, the boot animation handoff to the first frame). |
radial | Reveal expands outward from center. Reads as birth. Use for surfaces that “appear into being” — cart-load, a new CIPHER fragment arrival on the OLED. |
random | Cells reveal in random order. Reads as static collapsing to signal. Use for lower-stakes surfaces; matches the no-more-secrets decryption-reveal sibling. Project default. |
| Param | KN-86 default | Range | Notes |
|---|---|---|---|
type | random | diagonal | radial | random | Project-default random; carts override per use case |
duration | 0.4 | 0.1–4.0 | Total seconds for the full reveal. |
Composition with existing KN-86 visual effects
Section titled “Composition with existing KN-86 visual effects”The Batch 4 + 5 synthesis already committed to three signature visual effects. This spec composes with all three:
| Existing affordance | Interaction with this spec |
|---|---|
| Boot animation (BOOTSTRA.386 + AetherTune) | The boot animation can use the reveal:diagonal pattern from this spec to hand off into the first interactive frame |
reveal-text (no-more-secrets decryption reveal) | Same family as reveal:random here. Unify under one NoshAPI surface — (reveal ...) takes a :style arg covering both the per-cell ramp-flicker (no-more-secrets) and the ramp-by-position (react-video-ascii) cases. |
| CRT power-off animation (AetherTune) | The power-off shrink uses the inverse of reveal:radial — unreveal:radial collapses the frame inward. |
| Carousel auto-rotation (blessed-contrib) | Each carousel tab change uses reveal:diagonal for the inbound tab. |
NoshAPI surface — proposed primitives
Section titled “NoshAPI surface — proposed primitives”The cart-author API exposed through NoshAPI (ADR-0005) for the ASCII-effect grammar. Names indicative; exact symbols to be finalized in the FFI catalog.
(draw-ascii x y w h image &key chars char-mode brightness saturation bg-opacity crop-focus) ;; Render a raster image as ASCII into the rectangle (x y w h).
(ascii-effect surface :hover :brighten :trail-len 15 :brightness 2.0 :radius 0.08 :duration 1.0) ;; Attach a hover effect to an existing ASCII surface.
(ascii-effect surface :click :ripple :speed 3.0 :brightness 1.1 :duration 1.0) ;; Attach a click/commit effect.
(reveal surface :random :duration 0.4) ;; Apply the load-reveal animation. Composes with no-more-secrets style via :style flag.
(unreveal surface :radial :duration 0.6) ;; Inverse — collapse the frame inward.What this changes in the v0.1 ship list
Section titled “What this changes in the v0.1 ship list”Promote items from the Batch 4 features matrix:
- Item 45 (NoshAPI reveal-text primitive) absorbs into the
(reveal ...)primitive specified here, with:style :char-flickeras the no-more-secrets variant - Item 9 (CIPHER-LINE visualizer) can now also use
(draw-ascii ...)for cart-driven non-FFT imagery on the OLED - New item 58: ASCII-effect grammar shipped in v0.1 with the default ramp,
charMode: shape, and the three event types (hover, click, reveal). See features-matrix update.
Open questions
Section titled “Open questions”numColsdefault — react-video-ascii’s project-canonical default is 250; this spec sets KN-86 to 150 because 150 reads better against the AMBER monochrome aesthetic at the 80×25 panel scale. Worth validating with operator-eye test on the hardware bring-up; may move to 100 or 200 depending.scatterCharsfor KN-86 — react-video-ascii ships'->o'as the default scatter set. KN-86 may want a CP437-friendly alternative (e.g.,' ░▒▓'blocks-only set) for a more period-correct retro feel.- Hover translates how, exactly? — KN-86’s input is keyboard-only. The current spec says “the cell the focus cursor overlaps.” But many surfaces don’t have a visible cursor; the focus is implicit (e.g., the currently-highlighted list row). Need to define focus-cell semantics surface-by-surface or commit to a deck-wide focus-cell rule. Park for runtime-spec PR.
- Performance budget on the Pi Zero 2 W — react-video-ascii is WebGL2-accelerated; the Pi doesn’t have that. The engine reference is libcaca, which is CPU-only and well-suited to the Pi. Expect a frame-rate ceiling lower than the 60 Hz the react-video-ascii demo achieves; measure on hardware and document the realistic cap (likely 15–30 Hz for full-screen ASCII rendering on the Pi).
References
Section titled “References”- react-video-ascii — the prop matrix this spec distills from
- libcaca — the C image→ASCII engine reference
- asciinator (Batch 3) — halfblock-
▀rendering technique - no-more-secrets (Batch 4) — the decryption-reveal effect this spec composes with
- Batch 4 features matrix — the promote-to-spec items this updates
- inspiration/index.md — the synthesis updated for Batch 6