Skip to content

ADR-0021: Legacy Terminal mode (system-image escape hatch)

Notion task: GWP-269 (research) closed; this ADR ratifies its conclusions for implementation.


The KN-86 Deckline is a curated game appliance — production mode is a hardened kiosk with a read-only rootfs, no shell escape, no SSH (kiosk-mode.md). That posture is correct for the operator-facing experience. It also means there is no sanctioned path for Josh, the device’s owner, to run unrelated software on his own hardware.

The GWP-269 research memo evaluated a hidden Legacy Terminal mode: an entry gesture that hands the framebuffer, input, and audio to a child process running classic text-mode software, then reclaims them on exit. The memo concluded the architecture is feasible, identified four candidate titles (NetHack, Umoria, DrugWars, NFL Challenge), and surfaced seven open questions that gate implementation. This ADR resolves those seven questions and locks in the architecture so engineering can proceed.

  • Sprint-1 implementation work begins this week. Without an ADR, every engineering story re-debates the same decisions. Lock once.
  • The kiosk-mode posture is being treated as inviolable elsewhere in the platform. Any feature that brushes against kiosk-mode.md or ADR-0011 needs explicit ADR coverage to avoid implicit erosion of the lockdown.
  • Personal-use scope must be made explicit. Without it, future contributors may confuse Legacy Terminal with a marketed feature and pull it into the public-facing surface.
  • Production kiosk authority is preserved. Legacy Terminal runs inside nOSh’s authority (nOSh spawns the child); it does not bypass auto-login, does not require dev-mode, does not enable shell escape. The kiosk contract is unchanged.
  • Read-only rootfs. All Legacy Terminal binaries and license materials live under /opt/legacy-terminal/ baked at image-build time; no runtime install path.
  • Save state isolation per ADR-0011 §Risks #5. Per-title save data lives on /home/shared/legacy-terminal/saves/ (p6 partition), survives every A/B slot swap, never written by the flasher.
  • Personal-enjoyment feature for Josh’s own device. Not a launch capability, not marketed, not GTM-tracked.

We will implement Legacy Terminal, a hidden mode entered by a Bare Deck SYS+INFO×4 gesture, that suspends nOSh, hands display/input/audio to a child process running one of three bundled native ARM Linux binaries (NetHack, Umoria, Dopewars) or a side-loaded DOS executable via white-labeled DOSBox Staging, and resumes nOSh on child exit.

Concrete commitments:

  1. Mode-swap architecture. nOSh releases the SDL/KMSDRM framebuffer, releases evdev input grab, sends PSG_SILENCE to the Pico 2 coprocessor, pauses HUD render of Rows 0/24, then posix_spawns the child under the same nosh user. On SIGCHLD, nOSh reclaims the resources in inverse order and re-renders the HUD. No co-execution. No FFI bridge. No shared rendering.

  2. Entry gesture. From Bare Deck (no cartridge loaded), holding SYS and tapping INFO four times within 3 seconds enters the Legacy Terminal launcher. Reuses the input event model from ADR-0016 §9.

  3. Storage layout baked at image build (per system-image-build.md stage-kn86-runtime):

    /opt/legacy-terminal/ (read-only rootfs)
    ├── bin/ nethack, umoria, dopewars, dosbox-staging
    ├── share/ per-title data
    ├── titles/ *.manifest (4 files)
    ├── LICENSE NGPL + GPL-3 + GPL-2 + GPL-2 (DOSBox) concatenated
    └── src/ upstream source archives — GPL/NGPL §3a compliance
    /home/shared/legacy-terminal/ (p6 — persists across A/B slot swaps)
    ├── saves/ nethack, umoria, dopewars subdirs
    └── sideloads/ user-dropped DOS executables
  4. Bundled titles.

    • NetHack 3.6.7 — NGPL, native ARM Linux build via hints/linux.
    • Umoria 5.7.x — GPL-3-or-later, native ARM Linux via CMake.
    • Dopewars — GPL-2, native ARM Linux via Debian package, labeled in the launcher as “DOPEWARS (DrugWars-like)” with one-line attribution to John E. Dell.
    • Side-load via DOSBox Staging — GPL-2 white-labeled launcher chrome (engine pristine), routed for *.EXE / *.COM files dropped in /home/shared/legacy-terminal/sideloads/.
  5. Original DrugWars and NFL Challenge are NOT bundled. Both have unresolved IP — Dell retains DrugWars copyright with no formal grant; XOR Corporation (NFL Challenge) dissolved circa 1992 with no successor rights holder. NFL Challenge runs via the side-load path when the operator supplies their own .EXE. Original DrugWars: not supported in v1; substituted by Dopewars.

  6. Numpad remap shim (phone → calculator) lives in the Legacy Terminal launcher, not in nOSh’s input.c. Per-title profile selected from manifest field numpad_profile. NetHack number_pad:1 and Umoria’s original mode both expect calculator-layout numpad (7-8-9 top); KN-86 is phone-layout per ADR-0016 §5. The launcher translates scancodes before passing to the child PTY.

  7. F-key letter overlay rides on CIPHER-LINE Row 4, repurposed for the duration of any active Legacy Terminal session (launcher visible OR title in foreground). Mode-specific row reuse, parallel to the ADR-0016 §4 nEmacs reuse of CIPHER-LINE Row 4 as the multi-tap echo strip. CIPHER scrollback on Rows 2–3 pauses while Legacy Terminal is active.

  8. Audio policy v1: mute. The Pico 2 receives PSG_SILENCE at session entry and PSG_RESUME at exit. None of the four titles need audio. Routing PC-speaker / AdLib / Sound Blaster output through the MAX98357A is deferred indefinitely — revisit only if a specific bundled title’s audio becomes gameplay-critical, which is not foreseeable for the curated set.

  9. Side-load discovery is extension-based: *.EXE and *.COM route through DOSBox Staging; anything else is unsupported in v1. The user drops files into /home/shared/legacy-terminal/sideloads/; the launcher scans and lists them as launchable entries under the SIDE-LOAD… menu item.

  10. GPL/NGPL compliance is satisfied by:

    • LICENSE at /opt/legacy-terminal/LICENSE containing the full text of NGPL, GPL-2, and GPL-3 with attribution headers per project.
    • Upstream source archives at /opt/legacy-terminal/src/<project>-<version>.tar.gz (GPL §3a — machine-readable source accompanying object code).
    • Modifications scoped to launcher chrome only (splash text, autoexec hooks, branded amber color profile). Engine source remains upstream-pristine.
    • Operator-visible attribution: launcher splash includes “DOSBox by The DOSBox Team — GPL-2” and equivalent lines for the other GPL/NGPL projects.
  11. Image-build footprint soft cap: 200 MB. Current estimate is ~50 MB for binaries + DOSBox + source archives. If the Legacy Terminal subtree crosses 200 MB total, revisit the (rest)/2 rootfs split in ADR-0011 §SD partition layout.


Option A: Cartridge-based easter egg (Lisp reimplementation) (rejected)

Section titled “Option A: Cartridge-based easter egg (Lisp reimplementation) (rejected)”

Reimplement the four titles as Fe Lisp cartridges per ADR-0001. The Null cartridge already has the “weird system tool” precedent and could host them.

Rejected because: reimplementing NetHack and Umoria in Fe Lisp is years of work for a personal-enjoyment feature. The whole point is “play the actual game.” A faithful reimplementation would be neither.

Require the operator to flip the device into development mode (per kiosk-mode.md, this requires physical SD card access) and run DOSBox from a normal Linux shell.

Rejected because: dev mode breaks the kiosk contract, requires opening the Pelican case, and turns a casual easter egg into a 10-minute ceremony. Misses the joy of “tap a hidden combo, get to play NetHack.”

Option C: nOSh-orchestrated mode swap (ACCEPTED)

Section titled “Option C: nOSh-orchestrated mode swap (ACCEPTED)”

nOSh remains in charge. It owns the gesture, suspends itself, spawns the child, reclaims on exit. The kiosk contract is unchanged because nOSh is still the only thing the kiosk user can launch — the child is just nOSh’s child.

Chosen because: it preserves the production-mode lockdown (no shell escape, no dev mode, no SSH), uses architecture patterns already validated by ADR-0011 (gesture-based mode entry, /home/shared save isolation), and treats Legacy Terminal as a curated capability rather than a lockdown bypass.


The Option C win is mostly architectural:

  • Kiosk contract preserved. Options A and C both preserve it; B does not. Option C wins on the “ship NetHack as NetHack” front where Option A loses.
  • Time-to-implement. A is years (reimplementation). B is hours (dev-mode toggle is already built) but spends the kiosk contract. C is one sprint of foundation + a Pi bench-up to un-stub system-image work.
  • GPL surface. Option A keeps everything in-house Lisp — no GPL exposure. Options B and C both ship GPL/NGPL binaries; the compliance work is the same in both. Option C is no worse than the alternatives that work.
  • Operator experience. A delivers reimagined-not-real games. B delivers real games but requires breaking out of the device. C delivers real games inside the diegetic frame. C is the only option that achieves what the brief is actually asking for.
  • Cost we accept: ~30 MB of DOSBox in the system image, a ~50 MB Legacy Terminal subtree, GPL source-availability obligations, the Pico 2 silence/resume protocol overhead, and the maintenance surface of a numpad remap shim per supported title.

  • Real games, real fiction. Josh runs NetHack on his own KN-86, in amber, on the actual hardware, without breaking the kiosk frame. The fiction stays intact.
  • Reusable mode-swap pattern. The suspend/resume primitives (framebuffer release, evdev release, Pico PSG silence, HUD pause) are useful beyond Legacy Terminal — any future “child process owns the device for a session” feature reuses them.
  • Save state isolation works for free. ADR-0011 already isolates /home/shared from every system update path. Legacy Terminal save state inherits that property without any new policy.
  • Side-load surface is a contained release valve. Operators who want to bring their own DOS games drop files into one directory and the launcher discovers them. No software-install ceremony, no dev mode, no risk to the rootfs.
  • GPL source-availability obligation. We ship the source archives on the rootfs. Any future DOSBox Staging update bumps the pinned version and the source archive both. Discipline, not difficulty.
  • DOSBox Staging perf on Pi Zero 2 W is unverified for ARM64 dynarec. Pre-1990 DOS games (the only intended workload) should run with ample margin — Pi 3-class CPU emulating ~60 MHz 486 — but bench-test before shipping.
  • Numpad remap shim is a per-title maintenance surface. Each new bundled title that uses calculator-layout movement needs an entry. Small cost; flagged so it doesn’t surprise.
  • CIPHER-LINE Row 4 contention. Row 4 is now claimed by three modes: standard CIPHER scrollback, nEmacs multi-tap echo (per ADR-0016 §4), and Legacy Terminal F-key overlay (per this ADR). The OLED renderer must arbitrate cleanly; document the precedence in oled.c.
  • Footprint creep risk. Legacy Terminal opens a door that future work could push through (“just add this one more game”). The 200 MB soft cap is a deliberate guard rail.
  • Sprint 1 stories (already opened in Notion under the KN86 tag): manifest parser, numpad remap shim, stage-kn86-runtime scaffolding, launcher TUI + SYS-tab gesture, F-key overlay + Row 4 repurpose, QA validation pass.
  • Pi bench-up un-stub task (deferred until hardware is ready): replace the 00-install-legacy-terminal.sh TODO stubs with real apt + DOSBox Staging build commands; run the 50-cycle bench-test matrix from ADR-0011 §Risks #1 against the Legacy Terminal subtree.
  • Side-load file-picker UI (post-Sprint 1): the v1 cut shows side-loads as a flat list; future work may add metadata (last-played, file size, custom display name from a sidecar .toml).
  • Audio routing v2 (deferred indefinitely): only revisit if a future bundled title needs audio gameplay-criticaly. Touches the Pico 2 coprocessor protocol per ADR-0017.

Documentation Updates (REQUIRED — part of the decision, not aspirational)

Section titled “Documentation Updates (REQUIRED — part of the decision, not aspirational)”

This ADR lands in a PR that includes — or has follow-up PRs in the same sprint that include — the following updates:

  • docs/adr/README.md — add ADR-0021 to the chronological index.
  • docs/_meta/definitive-articles.md — add ADR-0021 to the canonical ADR set.
  • CLAUDE.md — Key Documents table extended with a row for Legacy Terminal mode (linking to this ADR + the research memo).
  • docs/device/os/kiosk-mode.md — add a “Legacy Terminal exception” subsection clarifying that nOSh-spawned child processes are an explicit allowed shape (kiosk authority preserved); cross-ref to this ADR.
  • kn86-emulator/src/legacy_terminal/ — module created by the manifest, numpad shim, launcher, and F-key overlay Sprint 1 stories.
  • tools/sd-provision/pi-gen-stages/stage-kn86-runtime/legacy-terminal/ — created by the stage-scaffolding Sprint 1 story.
  • Project-wide grep for stale references to the canonical hardware spec, cartridge model, or kiosk policy that would be invalidated by Legacy Terminal — none expected, but the audit must run before this ADR is closed out.

The Sprint 1 QA validation story explicitly verifies the items above are completed before the sprint is reported done.


For most of the project we’ve held the line that the KN-86 is a curated game appliance — auto-login, read-only root, no shell escape, no apt-get, no rabbit holes. That posture is correct, and it stays correct. Legacy Terminal does not weaken it; it adds a single sanctioned escape hatch that nOSh itself opens and closes.

The design pattern is “mode swap, not co-execution.” nOSh stays in charge. It hears the entry gesture, hands the device to a child process, and takes it back when the child exits. The child sees a clean 80×25 amber terminal and a phone-numpad it can be told to read as a calculator-numpad. Save state lives where save state already lives — /home/shared on p6, which ADR-0011 already isolates from every update path.

The four titles span the 1980s text-game canon Josh actually wants to play — two roguelikes (NetHack, Umoria), one trade simulator (Dopewars, faithful to the 1984 DrugWars pattern), and a side-load path for the one DOS-only title with a clean enough install pattern (NFL Challenge, BYO .EXE). DOSBox Staging covers the side-load. Three of the four ship as native ARM Linux binaries because three of the four already have native ARM Linux ports — DOSBox is only there for the holdout.

The thing that makes this ADR easy to live with is that none of it is load-bearing for anything else on the platform. If we delete it tomorrow, nothing breaks. It is, by design, a personal-use easter egg that respects every architectural commitment we’ve already made — and reuses several of them so well that the implementation footprint is almost embarrassingly small.