Skip to content

KN-86 Legacy Terminal — Pi Bench-Test Plan

This document defines the validation matrix that gates un-stubbing the binary-install steps in tools/sd-provision/pi-gen-stages/stage-kn86-runtime/legacy-terminal/00-install-legacy-terminal.sh.

The install script currently exits 0 without installing any binaries (all apt-get and DOSBox Staging build steps are commented-out TODO(bench-bring-up) blocks). That stub posture was deliberate: the image must build and flash cleanly before the Pi hardware target is validated. Running this plan is the gate that turns the stubs into real install commands.

Three upstream documents govern scope and pass/fail thresholds:

  • ADR-0021 §11 — image-build footprint soft cap of 200 MB for the entire /opt/legacy-terminal/ subtree. The current estimate is ~50 MB (binaries + DOSBox + source archives). Crossing 200 MB triggers a revisit of the (rest)/2 rootfs split in ADR-0011 §SD partition layout.
  • docs/device/os/system-image-build.md §“Base Debian release pinning” — the kernel version and Raspberry Pi OS Lite (arm64) trixie release hash (per docs/adr/ADR-0026-pi-os-trixie-base-pin.md) are pinned in tools/sd-provision/pi-gen-stages/pi-gen-pin.env. The pin must be in place before this plan runs; any kernel bump after the test requires re-running the full matrix.
  • ADR-0011 §Risks #1 — the 50-cycle USB-MSC update stability regression. Legacy Terminal adds a non-trivial rootfs subtree; the 50-cycle test confirms the added footprint does not disturb the A/B slot-swap path.

All of the following must be true before the first test row is executed:

  1. Kernel pinned. tools/sd-provision/pi-gen-stages/pi-gen-pin.env records an explicit Raspberry Pi OS Lite (arm64) trixie kernel version (e.g., 6.x) and the pi-gen commit hash. Record these in the Sign-off section at the bottom of this document.

  2. pi-gen-pin.env in effect. The image build that produced the SD under test was run with pi-gen-pin.env sourced, not from a floating latest release. The bench engineer verifies this by checking the pi-gen-pin.env hash at the bottom of this document against the hash of the file used at build time.

  3. Pi Zero 2 W bench rig available. Device configured with HDMI + USB keyboard for interactive validation (non-kiosk mode); a USB serial console adapter is recommended but not required for most rows. Production-mode boot (kiosk mode) is not used for this plan — dev mode is acceptable for the bench session.

  4. USB-MSC bench tooling from ADR-0011 ready. The 50-cycle stress test script from ADR-0011 §Risks #1 must be on-hand and verified to work against a baseline image before the Legacy Terminal subtree is added. Running it against baseline first establishes that any failure in the regression row (row 9) is attributable to the Legacy Terminal delta, not a pre-existing flakiness in the test infrastructure.

  5. 00-install-legacy-terminal.sh un-stub patch prepared. Before running this plan the bench engineer should have a draft PR un-stubbing the install script (replacing the TODO(bench-bring-up) blocks with real apt-get and DOSBox Staging build commands). The plan validates that draft; it is not run against the stub.


Each row is a pass/fail gate. All rows must pass before the un-stub PR is merged.


What. Confirm that the meson + ninja DOSBox Staging build produces a working dosbox-staging binary on aarch64 Raspberry Pi OS Lite (arm64) trixie inside the pi-gen chroot.

Procedure.

  1. In the pi-gen chroot (or on the bench Pi after flashing the un-stub image), run:
    dosbox-staging -version
  2. Confirm output includes the DOSBox Staging version string (e.g., DOSBox Staging, version 0.82.0).
  3. Confirm the binary path resolves to /opt/legacy-terminal/bin/dosbox-staging (or the symlink registered in the manifest) — not a system-wide install path.

Pass criteria. dosbox-staging -version exits 0, prints the correct pinned version, binary is at the expected path.

Failure mode. If the meson build fails: check that libsdl2-dev and libsdl2-net-dev are present in the chroot. If the dynrec ARM64 core causes a compile error: fall back to -Dcore=dynrec (generic); re-measure perf in row 1b (performance smoke, out-of-scope for Sprint 1). If DOSBox Staging is later available in the trixie arm64 package archive at the pinned version: prefer apt-get install dosbox-staging over the build-from-source path to reduce image-build time; re-run this row to confirm.


What. Confirm that apt install nethack-console succeeds in the chroot and that the installed binary starts and exits cleanly on the bench Pi.

Procedure.

  1. Run nethack -X (no-color mode) from the terminal.
  2. Confirm the intro screen appears (version banner, player character setup prompt).
  3. Press Escape or answer startup prompts to exit; confirm process exits 0.
  4. Confirm the binary resolves through /opt/legacy-terminal/bin/nethack (symlink to /usr/games/nethack-console or equivalent).

Pass criteria. NetHack launches, displays the terminal-mode intro, exits cleanly. This is a “does it boot” check only — no gameplay validation.

Failure mode. If the package name differs on the pinned trixie release (e.g., nethack-x11 instead of nethack-console): update the install script and this document to name the correct package. If the package is not available in the pinned archive: pin to the explicit version and source-build; update TODO(bench-bring-up) comment in the install script accordingly.


What. Confirm that apt install umoria (or source build) produces a working umoria binary and that it starts and exits cleanly.

Procedure.

  1. Run umoria from the terminal.
  2. Confirm the Umoria intro / character selection screen appears.
  3. Quit cleanly (typically Q then confirm at the prompt); confirm exit 0.
  4. Confirm the binary resolves through /opt/legacy-terminal/bin/umoria.

Pass criteria. Umoria launches, shows the intro, exits cleanly.

Failure mode. If umoria is not packaged in the pinned trixie archive: build from the GitHub source at the version pinned in pi-gen-pin.env; update the install script accordingly.


What. Confirm that apt install dopewars produces a working binary and that it starts and exits cleanly in curses (no-network) mode.

Procedure.

  1. Run dopewars --no-network or the equivalent curses-mode invocation (check man dopewars for the correct flag on the packaged version).
  2. Confirm the curses UI appears (trade-screen or intro).
  3. Quit cleanly; confirm exit 0.
  4. Confirm the binary resolves through /opt/legacy-terminal/bin/dopewars.

Pass criteria. Dopewars launches in curses mode, displays the trade interface, exits cleanly.

Failure mode. If dopewars requires a network connection to start: look for --local, --player-name, or equivalent flags that bypass the network handshake. Document the exact invocation in dopewars.manifest (the launch_args field) before closing this row.


Row 5 — Save state on /home/shared/legacy-terminal/saves/

Section titled “Row 5 — Save state on /home/shared/legacy-terminal/saves/”

What. Confirm that per-title save state lands on the p6 partition (/home/shared/) and survives an nOSh-style session boundary.

Procedure.

  1. Launch NetHack via the Legacy Terminal launcher (or directly from the bench terminal for this pre-launcher test).
  2. Save the game (in-game S command) and exit.
  3. Confirm a save file exists under /home/shared/legacy-terminal/saves/nethack/.
  4. Run stat <save-file-path> and confirm the device field matches the p6 block device (/dev/mmcblk0p6 or equivalent label share).
  5. Launch NetHack again; confirm the save is offered for resumption.

Pass criteria. Save file exists in the expected path on p6; stat confirms the correct device; resumed game loads the saved state.

Failure mode. If NetHack writes its save to a path outside /home/shared/: check nethack.manifest save_dir field and the NetHack OPTIONS=savedir:... configuration. The manifest parser (ADR-0021 §3, implemented in kn86-emulator/src/legacy_terminal/manifest.c) maps save_dir to the appropriate $HOME or OPTIONS injection before launch — confirm that mapping is correct for the packaged version of NetHack.


What. Confirm that the launcher (once implemented post-Sprint 1) lists user-supplied executables from the sideloads directory.

Procedure.

  1. Drop a placeholder file named TEST.EXE into /home/shared/legacy-terminal/sideloads/.
  2. Open the Legacy Terminal launcher menu (SYS+INFO×4 gesture from Bare Deck).
  3. Confirm that TEST.EXE appears as a launchable entry under the SIDE-LOAD... item.
  4. Confirm that a file with an unsupported extension (e.g., TEST.BIN) does not appear.

Pass criteria. TEST.EXE appears in the side-load list; TEST.BIN does not.

Note. This row depends on the launcher UI (post-Sprint 1 work). If the launcher is not yet implemented at the time this plan is executed, defer this row to Sprint 2 bench validation and note the deferral in the sign-off.

Failure mode. If the launcher’s sideloads/ scan misses files: check the manifest parser’s side-load discovery path and file extension filter.


What. Confirm that the Pico 2 PSG is silenced during a Legacy Terminal session and resumes on exit, per ADR-0021 §8 (audio policy v1: mute).

Procedure.

  1. Start a session with nOSh running normally (a PSG-active state — either a cartridge loaded or the bare-deck attract mode if it emits PSG tones).
  2. Enter the Legacy Terminal launcher via the SYS+INFO×4 gesture.
  3. Expected: PSG audio stops. If a bench audio probe is available per docs/software/api-reference/grammars/coprocessor-protocol.md (the PSG_SILENCE frame monitoring path), confirm receipt of the PSG_SILENCE UART frame from the Pi to the Pico 2. If no bench probe is available: speaker-listen (confirm silence at the speaker).
  4. Exit the Legacy Terminal launcher (or allow a title to exit).
  5. Expected: PSG audio resumes. Confirm receipt of PSG_RESUME frame, or speaker-listen for audio restart.

Pass criteria. Audio silences on launcher entry, resumes on exit. No PSG bleed during the Legacy Terminal session.

Failure mode. If PSG does not silence: check that coproc_send_psg_silence() is called in the launcher’s session-entry path (see kn86-emulator/src/legacy_terminal/launcher.c). If PSG does not resume: check that PSG_RESUME is sent in the session-exit path (both clean exit and SIGCHLD-triggered reclaim). If neither frame is visible on the UART: verify the UART baud rate (1 Mbps) and Pico 2 firmware reception using the coprocessor-protocol.md bench probe procedure.


What. Confirm that the installed /opt/legacy-terminal/ subtree fits under the ADR-0021 §11 soft cap of 200 MB (target: ~50 MB).

Procedure.

  1. After a full un-stub image build (all binaries installed, DOSBox Staging built, source archives downloaded):
    du -sh /opt/legacy-terminal/
  2. Record the measured value in the Sign-off section below.

Pass criteria. du -sh /opt/legacy-terminal/ reports under 200 MB. Ideally under 60 MB (current estimate: ~50 MB). If the measurement is between 60 MB and 200 MB: log the breakdown by subdirectory (bin/, share/, src/, titles/) and file a Notion task to investigate oversized components.

Failure mode. If footprint exceeds 200 MB: revisit the (rest)/2 rootfs split in ADR-0011 §SD partition layout per ADR-0021 §11. Most likely cause is a large DOSBox Staging source archive or unexpected game data directories. Strip build-time intermediate artifacts from src/ before measuring.


What. Re-run the ADR-0011 §Risks #1 50-cycle USB-MSC update stability test against the image that includes the Legacy Terminal subtree, to confirm that the added rootfs footprint did not disturb the A/B slot-swap path.

Procedure.

  1. Using the bench tooling established in the ADR-0011 pre-condition: run 50 complete A→B and B→A slot-swap cycles on the Legacy Terminal image.
  2. After each cycle: confirm that /home/shared/legacy-terminal/saves/ and /home/shared/deckstate.bin are intact (the flasher never touches p6).
  3. Confirm that the Legacy Terminal binaries at /opt/legacy-terminal/bin/ are intact in the active slot after each cycle.

Pass criteria. All 50 cycles complete without corruption to p6 or the active slot’s /opt/legacy-terminal/ tree. Same pass threshold as the baseline ADR-0011 test.

Failure mode. If cycles fail: isolate whether the failure is in the A/B swap logic (pre-existing, report to the update-system track) or in the Legacy Terminal subtree specifically (file path conflict, fstab entry conflict, overlayroot interaction). Re-run baseline without Legacy Terminal installed to determine attribution.


RowDescriptionGate condition
1DOSBox Staging buildBinary at expected path, -version exits 0
2NetHack runLaunches, displays intro, exits 0
3Umoria runLaunches, displays intro, exits 0
4Dopewars runLaunches in curses mode, exits 0
5Save state on p6Save file on /home/shared/, stat confirms p6 device
6Side-load discoveryTEST.EXE listed, unsupported ext not listed
7Audio mutePSG silent during session, resumes on exit
8Footprint checkdu -sh /opt/legacy-terminal/ < 200 MB
950-cycle USB-MSC regressionAll 50 cycles pass, p6 intact

All 9 rows must pass (or row 6 explicitly deferred with a Sprint 2 tracking task) for the un-stub PR to be considered greenlit.


If a row fails, the default disposition is:

  • Kernel / package version issue — update the pin in pi-gen-pin.env and rebuild the test image.
  • Package unavailable in pinned archive — add a source-build step to the install script; update this document with the build procedure.
  • Footprint overage — file a Notion task; investigate per-component breakdown before revising the ADR-0011 partition layout.
  • 50-cycle regression — halt un-stub; open a blocker against the update-system track before proceeding.
  • Audio / UART issue — check the coprocessor UART path per coprocessor-protocol.md before modifying any nOSh source code.

Do not merge the un-stub PR until all blocking rows pass. Row 6 (side-load discovery) may be deferred to Sprint 2 with explicit tracking.


To be filled in by the bench engineer at the time the plan is executed:

FieldValue
Date executed
Bench engineer
Kernel version
Raspberry Pi OS Lite (arm64) trixie release
pi-gen-pin.env SHA256
du -sh /opt/legacy-terminal/ measured footprint
All rows pass? (Y / N / partial — list deferred rows)
Notes