Files
LithosAnanake/docs/v4.0.0/plans/2026-10-26-stick-1-screen-and-keyboard.md
T
rajamesandClaude Opus 5.5 6f96502fdf docs(v4): the stick, increment 1 plan: v4's screen and keyboard
Approved by Captain Bob 2026-10-26.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 13:47:11 -04:00

8.3 KiB

The stick, increment 1: v4's screen and keyboard — Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:executing-plans (Captain Bob prefers native execution). Steps use checkbox (- [ ]) syntax for tracking.

Goal: On the v4 path the screen is dark until Zuse logs in and is then the console, and the PC's keyboards (PS/2, USB, and virtio on aarch64/riscv64) type to it as they do to v3's console.

Architecture: v3's keyboard bridge (repl.c's key tables, lock state and per-tick USB servicing) moves into a HAL file both paths call, unchanged in behaviour. The console gains a switch for whether text is mirrored to the framebuffer; the v4 boot brings the framebuffer up with the mirror off, turns it on (and TTF, and the cursor) when Zuse logs in, and turns it off and clears the screen when she logs out. The splash itself is increment 7; until then "splash only" is a dark screen.

Tech Stack: freestanding C99 kernel (kernel/), QEMU (OVMF/AAVMF/OpenSBI), QMP (send-key, input-send-event, screendump), Python 3 for the session harness.

Spec: docs/v4.0.0/STICK.md §2, §5.3, §5.4, §9, §10, §11 increment 1. The identity gate: docs/v4.0.0/plans/2026-10-26-engine-step4-identity-gate.md and its final review.

Global Constraints

  • Branch StarForth-v4.0.0, main checkout; no branch, no stash, never --no-verify. Commit with the LFS filter off (git -c filter.lfs.process= -c filter.lfs.clean=cat -c filter.lfs.smudge=cat -c filter.lfs.required=false); push with GitKraken's git-lfs on PATH. Messages end Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>.
  • Strict C99, -Wall -Wextra -Werror (.claude/CLAUDE.md Code Standards).
  • "Splash only, log on serial": before Zuse logs in, nothing the console prints reaches the screen.
  • v3's behaviour does not change (STICK.md §10); shown by a system=v3 boot typed to by keyboard.
  • "QEMU and bare metal ALWAYS hold parity": the keyboards and display QEMU gives are the same drivers a PC uses; no QEMU-only input path.
  • Never apply a fix that is not in this plan; report it.
  • Suite per task: make -C v4 test test-32 sanitize hosted-check (nothing in v4/ changes; it must still pass). Full acceptance on every kernel-affecting task (.claude/CLAUDE.md): three clean qemu boots to [zuse@Hera] ok> , POST 538/538, equal dict_hash (0xfec8c47cfc6f437f unless a task says otherwise), make -C v4 test-32 0 failures; logs committed.

Review Focus

  1. Keys pressed on a keyboard while headless are dropped, as serial bytes are (the gate's review item 4): sk_v4_identity_wait discards keyboard events too. Pinned in Task 2 (kbd_typeahead).
  2. Logging out leaves nothing of the session on screen: the screen is cleared when Zuse's session ends. Pinned in Task 2 (screen_dark_after_logout).
  3. A PC with no framebuffer (GOP gave none): v4 runs on serial alone and does not fault. Pinned in Task 2 (no_framebuffer, QEMU with -vga none on amd64).
  4. Alt+Tab toggles the graphics/text view on v4 as on v3 (the bridge's own interception moves with it). Covered by Task 1's move being byte-for-byte; checked by eye in Task 2's screendumps.
  5. v3's console still takes keys from every keyboard: Task 1's yardstick, a system=v3 boot typed to with send-key.

Task 1: The keyboard bridge, shared

Files:

  • Create: kernel/src/hal/kbd_input.c, kernel/include/starkernel/kbd_input.h
  • Modify: kernel/src/repl.c (remove the moved code; sk_console_getc_raw calls the new function)
  • Create: scripts/qemu_session.py (the session harness, kept in the repo from here on)

Interfaces:

  • Produces: int sk_input_getc(void); — one raw byte from the serial line or a keyboard (PS/2, USB boot keyboard, virtio), non-blocking, -1 when none; services the xHCI event ring once per timer tick while a USB keyboard runs; Alt+Tab toggles the graphics view and returns nothing. Exactly repl.c's sk_console_getc_raw() today, with sk_kbd_getc(), its tables, the Shift/Alt/Caps Lock/Num Lock state and sk_kbd_show_locks() moved with it.

  • Produces: void sk_input_drop(void); — read and drop every pending byte and key event (for Task 2's headless wait).

  • Produces: scripts/qemu_session.py MODE ARCH START_EPOCH [ARGS] — today's scratch gate_test.py (modes headless, zuse, hotplug, midpull, waitpull, typeahead, emergency) plus the modes added below; it prints PASS:/FAIL: lines and RESULT: PASS|FAIL, and quits QEMU over QMP.

  • Step 1: Test (the yardstick, before): add mode v3_keys to the harness: boot system=v3 with the default disks, wait for [zuse@Hera] ok> , type 3 4 + . with QMP send-key (one send-key per character, qcodes from QEMU's list; Enter ret), and check 7 ok follows. Run on amd64 (PS/2) and riscv64 (virtio keyboard). Expected: PASS both (this is the reference).

  • Step 2: Move the code; repl.c keeps g_console_pending_key and calls sk_input_getc().

  • Step 3: v3_keys again on amd64 and riscv64. Expected: PASS, the same lines.

  • Step 4: Suite; commit refactor(kernel): the keyboard bridge, shared with the four logs and scripts/qemu_session.py.

Task 2: v4's screen and keyboard

Files:

  • Modify: kernel/src/hal/console.c, kernel/include/starkernel/console.h
  • Modify: kernel/src/v4/sk_v4.c (the line editor reads sk_input_getc(); cursor), kernel/src/v4/sk_v4_identity.c (framebuffer and keyboards brought up; mirror on at login, off and cleared at logout; sk_input_drop() in the wait), kernel/src/kernel_main.c (the v4 branch passes the framebuffer: sk_v4_boot_info already holds it)

Interfaces:

  • Consumes: Task 1's sk_input_getc(), sk_input_drop().

  • Produces: void console_fb_mirror(int on); — whether console_putc also draws on the framebuffer; on by default, so v3 is unchanged. void console_fb_clear(void); — erase the screen and home the cursor (vt100_puts("\x1b[2J\x1b[H") when the framebuffer is up).

  • Bring-up order on the v4 path (in sk_v4_identity_boot, before xHCI): console_fb_init with the pixel format chosen as kernel_main.c's v3 path does, then console_fb_mirror(0); virtio_input_find_keyboard() (as the v3 path does; a no-op where there is none).

  • At login (where sk_v4_drive_arrived starts her session): console_fb_mirror(1), console_fb_enable_ttf(). At logout (sk_v4_drive_left): console_fb_mirror(0), console_fb_clear().

  • The v4 line editor: sk_input_getc() in place of console_getc(); the cursor shown at the prompt and after each echo or rub-out (console_fb_draw_cursor), and blinking every 50 ticks while idle, as sk_console_readline does.

  • Step 1: Failing tests (harness modes; amd64 unless named):

    • screen_dark_until_login: no Zuse drive; 30 s after POST: PASSED, QMP screendump (PPM); every pixel black. Today: FAIL (the loader's text is still on screen).
    • screen_console_after_login: hot-add Zuse; after [zuse@Hera] ok> , a screendump has at least 500 non-black pixels; type 3 4 + . by send-key; 7 ok on serial and a later screendump differs from the first. Today: FAIL.
    • screen_dark_after_logout: then device_del her drive; after "Zuse: session ended", a screendump is all black. Today: FAIL.
    • kbd_typeahead: no drive; send-key 3 4 + . while headless; hot-add Zuse; no 7 ok; then 1 2 + . by send-key gives 3 ok. Today: FAIL (keys are not read at all).
    • usb_kbd: QEMU_EXTRA="-device usb-kbd,bus=xhci0.0,port=3,id=ukbd"; after login, type 3 4 + . with input-send-event (device: ukbd); 7 ok. Today: FAIL.
    • no_framebuffer: amd64 with -vga none; the default disks; [zuse@Hera] ok> and 3 4 + . over serial gives 7 ok. Today: PASS (keep it passing).
    • virtio_kbd: riscv64 and aarch64, after login, send-key 3 4 + . gives 7 ok. Today: FAIL.
  • Step 2: Run each; record the failures.

  • Step 3: Build Task 2 as above.

  • Step 4: Every mode passes; the gate's sessions still pass (headless, zuse, hotplug, midpull, waitpull, typeahead, emergency); v3_keys still passes.

  • Step 5: Suite; full acceptance; commit feat(v4): the screen and the keyboards and the logs. Record in the commit message whether dict_hash changed (it should not: nothing on the node changes).