ADR-0052: Soft-glyph animation plane and the two-style animation model
Context
Section titled “Context”GWP-644 landed the animation substrate — an idle-timer registry (run-with-timer), a monotonic now, and read-key/poll-key — and the FIREPLACE attract screen proved it end to end: a doom-fire hearth that moves on the real 1024×600 amber framebuffer, driven by an idle-timer pumped from the host loop. The substrate (the “what makes it tick”) is solved.
What was missing is a deliberate answer to how a screen or cart should produce motion. FIREPLACE animates the only way the renderer allowed: per frame, for each cell, it picks a Code-Page shade glyph (░▒▓█) from a heat field and calls render/glyph. That is O(cells) of Lisp work per frame — one FFI call plus a per-cell decision, every cell, every frame — which on a tree-walking interpreter on a Pi Zero 2 W is the expensive part (the framebuffer pixel writes are cheap C; the Lisp call overhead and per-cell branching dominate).
A review of COMPUTE!‘s First Book of Atari Graphics (2026-06-27 brainstorm) surfaced the era’s signature technique: animate the lookup, not the pixels. On the Atari you redefine a character’s 8 bytes in RAM and every cell showing that character updates at once, for free, because the display hardware re-scans the font each frame. The KN-86 has no such hardware re-scan — but the philosophy maps onto a concrete, cheap primitive: a small bank of mutable glyphs plus a one-call tile-fill, so a uniform animated field costs O(1) of Lisp work per frame regardless of field size.
This gives the KN-86 two genuinely different, complementary animation primitives, and we want both to be first-class and clearly delineated so authors (and engineering agents) reach for the right one:
- SOFT-GLYPH — uniform, sub-cell motion across a whole field at
O(1)caller cost. The new primitive. - GLYPH-CYCLE — spatially-varying, whole-cell density fields at
O(cells)caller cost. Already shipped (this is what FIREPLACE does); no new code.
The forcing function: without a sanctioned model, every future animated surface re-invents motion as ad-hoc per-cell render/glyph loops — the one expensive style — even where a uniform texture (backgrounds, scanlines, dither, loaders) wants the cheap one.
Decision
Section titled “Decision”1. Add a soft-glyph plane to the renderer (render.c/render.h), addressed in its OWN slot space — NOT as KN-86 Code Page codepoints.
A small static bank of RENDER_SOFT_SLOTS (= 16) mutable 8×8 glyphs, cleared by render_init. Three pure C entry points, sharing the existing 8×8 blit path with render_glyph (one blit_glyph_bytes helper):
render_soft_define(slot, bytes[8])— overwrite a slot’s 8 rows (MSB-first, same layout askn86_font).render_soft_glyph(x, y, scale, slot, colors)— blit one slot (pixel coords; the soft-plane mirror ofrender_glyph).render_soft_fill(col, row, cols, rows, scale, slot, colors)— tile one slot across a cell region in a single call. This is what makes a uniform animated fieldO(1)caller work.
2. Expose it as a render-tier FFI family (render/soft-*) in sys_render.c, alongside render/glyph, System-tier only (carts get it via the same capability path as the rest of render/*): render/soft-slots, render/soft-define, render/soft-glyph, render/soft-fill.
3. Adopt the two complementary styles as the sanctioned animation model, documented in animation.md with the cost/capability trade and a worked reference (animlab):
| Style | Primitive | Cost / frame | Motion | Use for |
|---|---|---|---|---|
| Soft-glyph | render/soft-define + render/soft-fill | O(1) (redefine 8 bytes + 1 fill) | sub-cell, uniform across the field | backgrounds, scanlines, barber-poles, water, dither, loaders |
| Glyph-cycle | render/glyph per cell | O(cells) | whole-cell, spatially varying | fire, dissolve, heatmaps, per-cell reveals |
They compose — a screen may use both (the animlab reference does). Both are driven by the GWP-644 idle-timer substrate; neither introduces a new ticking mechanism.
Options considered
Section titled “Options considered”- Separate plane (chosen). Soft glyphs live in their own slot id space; the Code Page and the
render_glyphhot path are untouched; no scarce Layer-1 codepoints consumed. Cost: a soft glyph cannot be dropped inline inside atext-putsstring — it is drawn via the dedicatedrender/soft-*calls. Cleanest v1; the text path stays pure. - Codepoint aliasing (rejected for v1). Reserve a RAM-backed Code Page range so a redefined glyph draws anywhere a normal glyph does — including inside text — the literal Atari trick. Rejected now because Layer-1 (256 glyphs) is nearly full (see
character-set.md§2: only scattered “spare/reserved” slots remain), and it adds a range check to every text blit. The plane can later alias a codepoint range onto the same bank if inline-in-text soft glyphs are wanted, without changing the storage. - Player/Missile-style pixel overlay (deferred). A separate sprite layer floating at sub-cell pixel resolution with collision/priority (the other half of the Atari book). A real future primitive for smooth free motion (cursors, reticles, the Null-cart CIPHER ghost), but orthogonal to field animation and out of scope here.
Consequences
Section titled “Consequences”- Cheap full-field motion is now possible. A uniform animated background of any size costs two Lisp calls per frame instead of one-per-cell — the class of effect (dither, scanlines, ambient phosphor texture, loaders) that was previously prohibitively expensive on the device.
- One blit path.
render_glyphandrender_soft_glyphshareblit_glyph_bytes; no duplicated glyph-rendering logic. - No Code Page churn.
character-set.mdis unchanged — the soft plane is a separate address space, documented inanimation.md, not a codepoint reservation. - Small static cost. 16 × 8 = 128 bytes of static RAM for the bank; negligible.
- Authoring guidance is explicit.
animation.md+ this ADR give authors and engineering agents a clear “which style, when,” so new surfaces stop defaulting to the expensive per-cell loop. - Dither is now the natural KN-86 path to apparent extra brightness levels on the single-foreground amber phosphor (a soft slot animating a dither pattern) — the monochrome analog of the book’s NTSC artifacting.
Action items
Section titled “Action items”-
render.c/render.h: soft-glyph bank +render_soft_define/render_soft_glyph/render_soft_fill;render_initclears it; sharedblit_glyph_bytes. (Tests intest_render.c.) -
sys_render.c:render/soft-slots/render/soft-define/render/soft-glyph/render/soft-fillbound System-tier. (Tests intest_sys_render.c, incl. the cart-context capability split.) -
runtime/programs/animlab/animlab.lsp: the two-style reference screen, wired intoscreen_router.ck_libs;test_animlab.c; recordedruntime/tools/kn86rec/demos/animlab.gif. -
software/cartridges/authoring/animation.mdauthoring guide; registered in the authoring index. - Engineering-agent pointer added to the runtime
CLAUDE.mdconventions. - (Future) Consider codepoint aliasing onto the soft bank if inline-in-text soft glyphs are wanted.
- (Future) Player/Missile-style pixel overlay layer (sprites + collision/priority) — separate ADR.