starkernel: REPL scrollback, ~1000 lines (FABRIC.md item 4.4q)

Scope decided with Captain Bob before implementation: ring buffer +
recall on today's full-screen vt100 grid, not also confining REPL text
to the 4.4o 640x480 box (that confinement stays open as its own future
item, not a third silent deferral). No keyboard input path exists yet
(M8 unstarted), so the trigger is two new FORTH words, SCROLL-BACK
( n -- ) / SCROLL-FWD ( n -- ), exercised via serial injection.

vt100.c gains a text-only 1000-line ring buffer (kmalloc'd, tens of KB
-- not pixel snapshots, which would be ~1000x larger for no benefit)
plus a shadow buffer mirroring the current screen. scroll_up() now
pushes evicted rows into the ring before the pixel scroll. History is
one continuous sequence (ring then shadow); scrolling always redraws
from that sequence -- no separate pixel-scroll path for scrollback,
decided up front to avoid retrofitting later.

New src/word_source/scroll_words.c (Module 31), thin wrappers over
console_fb_scroll_back()/_fwd() -> vt100_scroll_back()/_fwd(). Bug
caught during live testing: both words initially used an off-by-one
underflow check (dsp < 1) copied from a different, older dsp
convention elsewhere in this codebase; vm_pop() (which these words
actually call) uses dsp as a 0-based top-of-stack index, so the check
rejected every legitimate single-argument call. Fixed by removing the
separate precheck and relying on vm_pop()'s own guard.

Live-verified on all three architectures (exceeds this item's
amd64-minimum bar): generated 50+ lines via a FORTH loop, confirmed
SCROLL-BACK recovers correctly older content, and on amd64 confirmed
SCROLL-FWD returns to genuinely live state (not a frozen snapshot) by
showing the injected commands' own echo. Known limitation confirmed by
direct pixel measurement: redrawn lines lose their original SGR color
(not stored per-cell) -- text recovers exactly, color does not.

Three-arch verified: Failed: 0, dict-hashes identical across all
three (values changed correctly from prior items -- two new words
were added).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-08-11 21:23:25 -04:00
co-authored by Claude Sonnet 5
parent 277845e3bf
commit 1f3ec3554e
27 changed files with 73487 additions and 112 deletions
+67 -1
View File
@@ -5613,7 +5613,7 @@ document and committing that amendment as its own item.*
> Probe code reverted from `repl.c` immediately after screendump capture; tree confirmed
> clean.
- [ ] **4.4q — Scrollback, ~1000 lines.** A circular buffer of prior scroll-box lines, target
- [x] **4.4q — Scrollback, ~1000 lines.** A circular buffer of prior scroll-box lines, target
depth approximately 1000 (Captain Bob's own qualification: "something along those lines,"
not a hard-locked spec number). Storage mechanism (`kmalloc` ring buffer vs. static array)
is this item's own implementation call.
@@ -5622,6 +5622,72 @@ document and committing that amendment as its own item.*
+ logs per CLAUDE.md.
*Refs:* §27.8.
> **Scope decided with Captain Bob before implementation, 2026-08-11:** this item is the
> ring buffer and recall mechanism on today's full-screen `vt100` grid, not also confining
> REPL text to the 4.4o 640×480 box. That confinement (`vt100`'s grid still spans the whole
> screen, per 4.4p's "observed, not fixed") stays open, explicitly, as its own future item —
> not a third silent deferral. Trigger mechanism: no keyboard input path exists yet
> (`console_getc()` confirmed serial-only; M8 is unstarted) — implemented as two FORTH
> words, `SCROLL-BACK ( n -- )` / `SCROLL-FWD ( n -- )`, exercised via the same serial
> injection technique 4.4k used.
>
> **Done 2026-08-11.** `vt100.c` gained a text-only ring buffer (`g_ring`,
> `VT100_SCROLLBACK_MAX_LINES` = 1000, `kmalloc`'d in `vt100_enable_ttf()` — text, not
> pixels: at ~cols bytes/line this is tens of KB, roughly three orders of magnitude smaller
> than a pixel-snapshot ring for the same depth would be) plus a `g_shadow` buffer mirroring
> exactly what's on screen (updated by every `draw_cursor_glyph()` call and shifted by every
> `scroll_up()`, which now pushes evicted rows into the ring before the pixel scroll).
> History model: one continuous sequence, ring (oldest→newest evicted) then shadow
> (oldest→newest on-screen); `g_scroll_offset` selects a window into it and
> `scrollback_redraw()` draws that window. Scrolling is always "redraw from the ring/shadow
> history," never a second pixel-scroll mechanism — decided up front per this document's own
> advisory review, since retrofitting a redraw model onto a blit-scroll model later would
> have been the expensive order.
>
> New words in `src/word_source/scroll_words.c` (registered as Module 31,
> `word_registry.c`), thin wrappers over new `console_fb_scroll_back()`/`_fwd()`
> `vt100_scroll_back()`/`_fwd()`. **Bug caught during live testing, not left in:** both words
> initially used `if (vm->dsp < 1)` for a single-argument underflow check, copied from a
> pattern that turned out to belong to a different, older `dsp`-as-raw-count convention
> elsewhere in this codebase; the convention `vm_pop()` (which these words actually call)
> uses is `dsp` as a 0-based top-of-stack index, where `dsp < 0` is empty — the off-by-one
> rejected every legitimate single-argument call as underflow. Fixed by removing the
> separate precheck entirely and relying on `vm_pop()`'s own guard, sidestepping the
> multi-convention mismatch rather than risking picking the wrong one again.
>
> **Live-verified on all three architectures**, not just amd64 (exceeds this item's own
> amd64-minimum bar): injected `: GENLINES 0 DO I . CR LOOP ; 50 GENLINES` (`DO`/`LOOP` are
> compile-only in this FORTH, needs a definition — also caught live, fixed the test not the
> code) to generate far more than one screen's worth of output — each iteration's HADES/ECW
> trace logging multiplies the line count well past 50 real terminal lines. amd64: captured
> three screendumps — (A) live tail showing lines up to 49, (B) after `30 SCROLL-BACK`
> showing lines 3844 (correctly older), (C) after `30 SCROLL-FWD` showing genuinely live
> content again, including the echoed `SCROLL-BACK`/`SCROLL-FWD` commands themselves and
> their `ok` responses — proof it tracks whatever is truly live "now" rather than a frozen
> snapshot, not just a coincidental match to an earlier capture. aarch64/riscv64: same
> `GENLINES`/`SCROLL-BACK` sequence, both correctly recovered older lines (46/47) after
> scrolling back 20. Screendumps:
> [evidence/amd64/qemu-screenshot-20260811-210852-4.4q-scrollback-A-live.png](evidence/amd64/qemu-screenshot-20260811-210852-4.4q-scrollback-A-live.png),
> [...-B-scrolled.png](evidence/amd64/qemu-screenshot-20260811-210852-4.4q-scrollback-B-scrolled.png),
> [...-C-restored.png](evidence/amd64/qemu-screenshot-20260811-210852-4.4q-scrollback-C-restored.png),
> [evidence/aarch64/qemu-screenshot-20260811-211338-4.4q-scrollback-A-live.png](evidence/aarch64/qemu-screenshot-20260811-211338-4.4q-scrollback-A-live.png),
> [...-B-scrolled.png](evidence/aarch64/qemu-screenshot-20260811-211338-4.4q-scrollback-B-scrolled.png),
> [evidence/riscv64/qemu-screenshot-20260811-212029-4.4q-scrollback-A-live.png](evidence/riscv64/qemu-screenshot-20260811-212029-4.4q-scrollback-A-live.png),
> [...-B-scrolled.png](evidence/riscv64/qemu-screenshot-20260811-212029-4.4q-scrollback-B-scrolled.png).
>
> **Known limitation, confirmed by direct pixel measurement, not just documented:** redrawn
> lines lose their original SGR color (per-cell color history isn't stored) — measured the
> amd64 restored-view screenshot's `[Hera]` prefix pixels directly: no orange found in any
> row touched by the `SCROLL-FWD` redraw, only in the one freshly-drawn live row after the
> redraw settled. Text content is recovered exactly; color is not — this is the tradeoff
> this item's own text left as "this item's own implementation call."
>
> Three-arch standard boot+log basis: amd64 (`logs/20260811-210705/amd64/`), aarch64
> (`logs/20260811-211155/aarch64/`), riscv64 (`logs/20260811-211617/riscv64/`) — `Failed: 0`
> all three, dict-hashes identical across all three (values changed from prior items,
> correctly — two new words were added to the dictionary — but still cross-arch consistent,
> which is the actual parity property that matters).
- [ ] **4.4r — Toggle word: hide/show the scroll box.** A FORTH word that hides the 640×480
scroll box, revealing the rest of CANVAS beneath it for drawing; showing it again restores
the scroll-box content undisturbed, no scrollback loss. Depends on 4.4o/4.4p (geometry) and