Skip to content

ADR-0052: Soft-glyph animation plane and the two-style animation model

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.

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 as kn86_font).
  • render_soft_glyph(x, y, scale, slot, colors) — blit one slot (pixel coords; the soft-plane mirror of render_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 field O(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):

StylePrimitiveCost / frameMotionUse for
Soft-glyphrender/soft-define + render/soft-fillO(1) (redefine 8 bytes + 1 fill)sub-cell, uniform across the fieldbackgrounds, scanlines, barber-poles, water, dither, loaders
Glyph-cyclerender/glyph per cellO(cells)whole-cell, spatially varyingfire, 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.

  • Separate plane (chosen). Soft glyphs live in their own slot id space; the Code Page and the render_glyph hot path are untouched; no scarce Layer-1 codepoints consumed. Cost: a soft glyph cannot be dropped inline inside a text-puts string — it is drawn via the dedicated render/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.
  • 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_glyph and render_soft_glyph share blit_glyph_bytes; no duplicated glyph-rendering logic.
  • No Code Page churn. character-set.md is unchanged — the soft plane is a separate address space, documented in animation.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.
  • render.c/render.h: soft-glyph bank + render_soft_define/render_soft_glyph/render_soft_fill; render_init clears it; shared blit_glyph_bytes. (Tests in test_render.c.)
  • sys_render.c: render/soft-slots/render/soft-define/render/soft-glyph/render/soft-fill bound System-tier. (Tests in test_sys_render.c, incl. the cart-context capability split.)
  • runtime/programs/animlab/animlab.lsp: the two-style reference screen, wired into screen_router.c k_libs; test_animlab.c; recorded runtime/tools/kn86rec/demos/animlab.gif.
  • software/cartridges/authoring/animation.md authoring guide; registered in the authoring index.
  • Engineering-agent pointer added to the runtime CLAUDE.md conventions.
  • (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.