Approved by Captain Bob 2026-10-26. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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 onPATH. Messages endCo-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>. - Strict C99,
-Wall -Wextra -Werror(.claude/CLAUDE.mdCode 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=v3boot 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 inv4/changes; it must still pass). Full acceptance on every kernel-affecting task (.claude/CLAUDE.md): threeclean qemuboots to[zuse@Hera] ok>, POST 538/538, equaldict_hash(0xfec8c47cfc6f437funless a task says otherwise),make -C v4 test-320 failures; logs committed.
Review Focus
- Keys pressed on a keyboard while headless are dropped, as serial bytes are (the gate's review
item 4):
sk_v4_identity_waitdiscards keyboard events too. Pinned in Task 2 (kbd_typeahead). - 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). - 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 noneon amd64). - 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.
- v3's console still takes keys from every keyboard: Task 1's yardstick, a
system=v3boot typed to withsend-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_rawcalls 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. Exactlyrepl.c'ssk_console_getc_raw()today, withsk_kbd_getc(), its tables, the Shift/Alt/Caps Lock/Num Lock state andsk_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 scratchgate_test.py(modesheadless,zuse,hotplug,midpull,waitpull,typeahead,emergency) plus the modes added below; it printsPASS:/FAIL:lines andRESULT: PASS|FAIL, and quits QEMU over QMP. -
Step 1: Test (the yardstick, before): add mode
v3_keysto the harness: bootsystem=v3with the default disks, wait for[zuse@Hera] ok>, type3 4 + .with QMPsend-key(onesend-keyper character,qcodes from QEMU's list; Enterret), and check7 okfollows. Run on amd64 (PS/2) and riscv64 (virtio keyboard). Expected: PASS both (this is the reference). -
Step 2: Move the code;
repl.ckeepsg_console_pending_keyand callssk_input_getc(). -
Step 3:
v3_keysagain on amd64 and riscv64. Expected: PASS, the same lines. -
Step 4: Suite; commit
refactor(kernel): the keyboard bridge, sharedwith the four logs andscripts/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 readssk_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_infoalready holds it)
Interfaces:
-
Consumes: Task 1's
sk_input_getc(),sk_input_drop(). -
Produces:
void console_fb_mirror(int on);— whetherconsole_putcalso 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_initwith the pixel format chosen askernel_main.c's v3 path does, thenconsole_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_arrivedstarts 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 ofconsole_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, assk_console_readlinedoes. -
Step 1: Failing tests (harness modes; amd64 unless named):
screen_dark_until_login: no Zuse drive; 30 s afterPOST: PASSED, QMPscreendump(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; type3 4 + .bysend-key;7 okon serial and a later screendump differs from the first. Today: FAIL.screen_dark_after_logout: thendevice_delher drive; after "Zuse: session ended", a screendump is all black. Today: FAIL.kbd_typeahead: no drive;send-key3 4 + .while headless; hot-add Zuse; no7 ok; then1 2 + .bysend-keygives3 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, type3 4 + .withinput-send-event(device: ukbd);7 ok. Today: FAIL.no_framebuffer: amd64 with-vga none; the default disks;[zuse@Hera] ok>and3 4 + .over serial gives7 ok. Today: PASS (keep it passing).virtio_kbd: riscv64 and aarch64, after login,send-key3 4 + .gives7 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_keysstill passes. -
Step 5: Suite; full acceptance; commit
feat(v4): the screen and the keyboardsand the logs. Record in the commit message whetherdict_hashchanged (it should not: nothing on the node changes).