FABRIC.md item 4.4ac: scrollback during boot/POST, not just from REPL

vt100.c: font_8x16/bitmap-mode boot output previously had no scrollback
at all -- g_shadow/g_ring were only allocated in vt100_enable_ttf(), so
POST/self-test/heartbeat text was gone the instant it scrolled off,
recoverable only from the serial log. Gives boot mode its own ring/
shadow pair (bitmap cell geometry, 4096-line capacity), frozen as a
snapshot the moment vt100_enable_ttf() switches to the TTF-geometry
pair, per the two-independent-rings design scoped with Captain Bob.
scrollback_line_at()/scrollback_redraw()/vt100_scroll_back() now walk
all four segments (boot ring, boot shadow, TTF ring, TTF shadow) as one
continuous history, so PgUp from the REPL reaches back through POST.

Three-arch QEMU boot + logs clean (amd64/aarch64/riscv64, no faults, no
dictionary/parity regressions). Visual verification that PgUp actually
recalls POST text still needs an interactive GTK screendump -- noted as
open in FABRIC.md, same pattern as 4.4ab's screendump.

Also includes BLOCK_MAP.md/artemis.img regenerated by these builds, and
the acceptance-boot logs (plus stray logs from an earlier QEMU-instance
collision during testing -- kept per repo convention, logs are audit
artifacts, not deleted).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-08-12 18:21:31 -04:00
co-authored by Claude Sonnet 5
parent 149dbc4c73
commit 3396e9a7b4
10 changed files with 51614 additions and 15 deletions
+50
View File
@@ -6114,6 +6114,56 @@ document and committing that amendment as its own item.*
> `: MARGIN 30 SPACES CR ;`, both echoed correctly including shift-key punctuation) —
> confirms full-width text with no box confinement, no strip, no border artifacts.
- [ ] **4.4ac — Scrollback during boot/POST, not just from the REPL onward.** Scoped
2026-08-12, in conversation with Captain Bob. Found while investigating a separate report
("the framebuffer does not mimic the serial port"): `console_putc()` does mirror every byte
to both serial and framebuffer during boot, so that report wasn't a mirroring bug — it's
that 4.4q's scrollback buffers (`g_shadow`/`g_ring`, `vt100.c:146-150`) are only allocated
inside `vt100_enable_ttf()`, sized for the TTF cell grid. Boot/POST runs entirely in
`VT_GLYPH_BITMAP` mode against `font_8x16.c`'s fixed 8×16 grid, before that allocation ever
happens, so nothing typed to the framebuffer during boot is recoverable once it scrolls off
— only the serial log retains it.
**Confirmed while scoping: the write path is already mode-agnostic.** `draw_cursor_glyph()`
(`vt100.c:278`) and `scroll_up()` (`vt100.c:589`) both gate purely on
`g_shadow`/`g_ring != NULL`, with no dependency on which glyph renderer is active. This is
not a second recording mechanism to build — it's making the existing one exist earlier.
**Decisions locked with Captain Bob 2026-08-12:**
1. **Two independent ring buffers**, not a reflow-into-one. A boot-geometry ring/shadow
(8×16 cell stride, allocated in `vt100_init()`, `vt100.c:374`) sits alongside the
existing TTF-geometry ring/shadow (unchanged, still allocated in `vt100_enable_ttf()`).
Avoids re-wrapping stored boot lines to a different column width at the TTF transition,
and leaves the already-working TTF scrollback code untouched.
2. **Boot ring capacity: 4096 lines (guessed, not measured against a target).** Reasoning:
tonight's own qemu-esp serial log ran to ~8,500 lines of POST/Hermes-Artemis-self-test/
heartbeat output — 1000 lines (4.4q's current `VT100_SCROLLBACK_MAX_LINES`) would only
retain the last ~12% of that. 4096 is a 4x bump, cheap in kmalloc terms (at a typical
boot grid this is on the order of 100-200KB), and covers roughly half of tonight's
transcript on its own — closing the rest of the gap is expected to come from trimming
the actual POST/heartbeat verbosity (a separate, still-open report from the same
conversation), not from growing the buffer indefinitely. Revisit this number once that
verbosity work happens or is explicitly declined.
3. **One continuous scroll across the boot→REPL seam**, not two separately-triggered
histories. `scrollback_line_at()` (`vt100.c:465`) needs to walk boot-ring →
boot-shadow → TTF-ring → TTF-shadow, in that order, as one combined index space, so
PgUp/scroll-back from the live REPL eventually reaches all the way back through POST
without a separate command to switch buffers.
**Not decided here, deferred to implementation:** exact kmalloc call sites/error handling
for the new boot-mode buffers (mirror 4.4q's existing non-fatal-failure pattern at
`vt100.c:444-447`); how `scrollback_redraw()` (`vt100.c:480`) picks which stride/cell size
to redraw boot-ring lines at once TTF mode has taken over the screen (likely: redraw at
*current* mode's cell size regardless of origin, same "text recovers exactly, appearance
doesn't" limitation 4.4q already documents for color).
*Done when:* three-arch QEMU boot + logs clean; amd64 screendump after scrolling back from
a live REPL session shows POST/self-test text recovered, not just the pre-TTF tail.
*Refs:* §27.8, 4.4q.
> **Boot/logs half DONE 2026-08-12; screendump half still open.** Three-arch QEMU boot +
> logs clean, no regression: `logs/20260812-173850/amd64`, `logs/20260812-174418/aarch64`,
> `logs/20260812-175223/riscv64`. Not yet verified: an actual amd64 screendump showing
> recalled POST/self-test text via PgUp scrollback -- that needs a live GTK QEMU window and
> someone typing the scroll-back command interactively, the same way 4.4ab's screendump was
> captured directly by Captain Bob, not something doable from a headless acceptance boot.
- [ ] **4.5 — URGENT, flagged by Captain Bob 2026-08-11: the kernel build has never used any
compiler optimization.** **Blocking priority, set by Captain Bob 2026-08-11: no other
punch-list item is worked until 4.5a4.5f are done**, ahead of whatever would otherwise
+1 -1
View File
@@ -1,5 +1,5 @@
# Capsule Block Manifest — Auto-generated
<!-- Generated by mkcapsule --manifest 2026-08-12T20:41:54Z -->
<!-- Generated by mkcapsule --manifest 2026-08-12T21:51:55Z -->
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
<!-- Hand-written justifications and immutability notes live -->
<!-- in MANIFEST.md alongside this auto-generated index. -->
BIN
View File
Binary file not shown.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+125 -14
View File
@@ -144,12 +144,53 @@ static int g_terminal_visible = 1;
#define VT100_SCROLLBACK_MAX_LINES 1000u
static char *g_shadow = (void *)0; /* g_vt.rows * g_line_stride bytes */
static char *g_ring = (void *)0; /* VT100_SCROLLBACK_MAX_LINES * g_line_stride bytes */
static uint32_t g_line_stride = 0; /* cols + 1 (NUL terminator), fixed once TTF mode starts */
static uint32_t g_ring_count = 0; /* valid stored lines, <= VT100_SCROLLBACK_MAX_LINES */
static char *g_ring = (void *)0; /* g_ring_cap * g_line_stride bytes */
static uint32_t g_line_stride = 0; /* cols + 1 (NUL terminator) */
static uint32_t g_ring_count = 0; /* valid stored lines, <= g_ring_cap */
static uint32_t g_ring_head = 0; /* ring slot index of the oldest stored line */
static uint32_t g_ring_cap = 0; /* active ring capacity -- VT100_BOOT_SCROLLBACK_MAX_LINES
* pre-TTF, VT100_SCROLLBACK_MAX_LINES from vt100_enable_ttf()
* onward; see 4.4ac below */
static uint32_t g_scroll_offset = 0; /* 0 = live view */
/* -----------------------------------------------------------------------
* FABRIC.md item 4.4ac: boot-mode scrollback. font_8x16/bitmap-mode boot
* output (POST, capsule birth, self-tests, heartbeat) previously had no
* scrollback at all -- g_shadow/g_ring above were only ever allocated in
* vt100_enable_ttf(), so anything printed before the REPL took over was
* gone the moment it scrolled off-screen. This block gives boot mode its
* own ring/shadow pair, allocated in vt100_init() at the bitmap cell
* geometry (8x16), separate from the TTF-geometry pair above because the
* two modes have different cell sizes and therefore different
* g_line_stride -- reflowing one buffer's stored lines into the other's
* width was rejected as unnecessary complexity for a text-only recall
* (decided with Captain Bob 2026-08-12).
*
* g_boot_shadow/g_boot_ring are frozen (snapshotted, not appended to)
* the moment vt100_enable_ttf() runs: it points g_shadow/g_ring at fresh
* TTF-geometry buffers, so this file's four scrollback segments become,
* oldest to newest: boot ring, boot shadow (the last screenful of boot
* output, frozen as of the transition), TTF ring, TTF shadow (live).
* scrollback_line_at()/scrollback_redraw() walk all four as one
* continuous history, per Captain Bob's call that PgUp from the REPL
* should reach all the way back through POST, not stop at a seam.
*
* Capacity guessed, not measured: 4096 lines vs. the TTF ring's 1000.
* A real qemu-esp boot transcript observed 2026-08-12 ran to ~8,500
* lines of POST/self-test/heartbeat output -- 4096 covers roughly half
* of that on its own; closing the rest of the gap is expected to come
* from trimming that verbosity (a separate, still-open item), not from
* growing this buffer indefinitely. Revisit if that trim doesn't happen. */
#define VT100_BOOT_SCROLLBACK_MAX_LINES 4096u
static char *g_boot_shadow = (void *)0; /* frozen boot g_shadow, snapshotted at vt100_enable_ttf() */
static char *g_boot_ring = (void *)0; /* frozen boot g_ring */
static uint32_t g_boot_line_stride = 0;
static uint32_t g_boot_ring_count = 0;
static uint32_t g_boot_ring_head = 0;
static uint32_t g_boot_ring_cap = 0;
static uint32_t g_boot_rows = 0; /* g_vt.rows at snapshot time; 0 if boot alloc failed */
/* -----------------------------------------------------------------------
* Parser state enum
* --------------------------------------------------------------------- */
@@ -386,6 +427,34 @@ void vt100_init(void)
g_vt.initialized = 1;
erase_display(2);
/* FABRIC.md item 4.4ac: allocate boot-mode scrollback now, at the
* bitmap cell geometry just computed above. Non-fatal on failure, same
* pattern as vt100_enable_ttf()'s TTF allocation below: g_shadow/g_ring
* stay NULL, draw_cursor_glyph()/scroll_up() already gate on that and
* skip scrollback tracking, so boot text just isn't recoverable --
* exactly the pre-4.4ac behavior, not a new failure mode. */
{
uint32_t i;
g_line_stride = g_vt.cols + 1u;
g_ring_cap = VT100_BOOT_SCROLLBACK_MAX_LINES;
g_shadow = (char *)kmalloc((size_t)g_vt.rows * g_line_stride);
g_ring = (char *)kmalloc((size_t)g_ring_cap * g_line_stride);
if (!g_shadow || !g_ring) {
log_message(LOG_ERROR, "vt100: boot scrollback buffer allocation failed, unavailable");
if (g_shadow) { kfree(g_shadow); g_shadow = (void *)0; }
if (g_ring) { kfree(g_ring); g_ring = (void *)0; }
} else {
for (i = 0; i < g_vt.rows; i++) {
uint32_t c;
char *row = g_shadow + (size_t)i * g_line_stride;
for (c = 0; c < g_vt.cols; c++) row[c] = ' ';
row[g_vt.cols] = '\0';
}
}
g_ring_count = 0;
g_ring_head = 0;
}
}
void vt100_enable_ttf(void)
@@ -414,6 +483,24 @@ void vt100_enable_ttf(void)
g_ttf_ready = 1;
}
/* FABRIC.md item 4.4ac: freeze the boot-mode scrollback buffers (if
* any -- vt100_init() may have failed to allocate them) before they
* get overwritten below. g_vt.cols/rows/g_shadow/g_ring/g_line_stride/
* g_ring_count/g_ring_head/g_ring_cap still hold boot-mode values at
* this point, nothing below has touched them yet. g_boot_rows is only
* set non-zero when the snapshot is actually usable -- if boot alloc
* failed, g_shadow is already NULL and g_ring_count is already 0 (
* scroll_up() never touches the ring when g_shadow/g_ring are NULL),
* so leaving g_boot_rows at 0 keeps scrollback_line_at()'s combined
* index space consistent instead of pointing into a NULL buffer. */
g_boot_shadow = g_shadow;
g_boot_ring = g_ring;
g_boot_line_stride = g_line_stride;
g_boot_ring_count = g_ring_count;
g_boot_ring_head = g_ring_head;
g_boot_ring_cap = g_ring_cap;
g_boot_rows = g_shadow ? g_vt.rows : 0;
g_glyph_mode = VT_GLYPH_TTF;
/* Full-screen grid, TTF cell size -- see this file's header comment.
@@ -439,8 +526,9 @@ void vt100_enable_ttf(void)
{
uint32_t i;
g_line_stride = g_vt.cols + 1u;
g_ring_cap = VT100_SCROLLBACK_MAX_LINES;
g_shadow = (char *)kmalloc((size_t)g_vt.rows * g_line_stride);
g_ring = (char *)kmalloc((size_t)VT100_SCROLLBACK_MAX_LINES * g_line_stride);
g_ring = (char *)kmalloc((size_t)g_ring_cap * g_line_stride);
if (!g_shadow || !g_ring) {
log_message(LOG_ERROR, "vt100: scrollback buffer allocation failed, scrollback unavailable");
if (g_shadow) { kfree(g_shadow); g_shadow = (void *)0; }
@@ -459,13 +547,32 @@ void vt100_enable_ttf(void)
}
}
/* Fetch the text of combined-history index `idx` (0 = oldest ring entry,
* g_ring_count + g_vt.rows - 1 = newest shadow row) -- see this file's
* 4.4q block for the ring-then-shadow history model. */
/* Fetch the text of combined-history index `idx` (0 = oldest boot-ring
* entry, ... newest TTF-shadow row = live) -- four segments back to back:
* boot ring, boot shadow (frozen at the boot->TTF transition), TTF ring,
* TTF shadow (live). See 4.4ac's block above for why boot and TTF each
* keep their own ring/shadow pair (different cell geometry, different
* g_line_stride) instead of one reflowed buffer, and 4.4q's block for the
* ring-then-shadow model each pair follows individually. Only ever called
* once TTF mode is active (scrollback is wired to a keyboard command that
* doesn't exist before the REPL starts -- console.c's
* console_fb_scroll_back()/_fwd()), so g_ring/g_shadow here always mean
* the TTF pair, never the boot one. */
static const char *scrollback_line_at(uint32_t idx)
{
if (idx < g_boot_ring_count) {
uint32_t slot = (g_boot_ring_head + idx) % g_boot_ring_cap;
return g_boot_ring + (size_t)slot * g_boot_line_stride;
}
idx -= g_boot_ring_count;
if (idx < g_boot_rows) {
return g_boot_shadow + (size_t)idx * g_boot_line_stride;
}
idx -= g_boot_rows;
if (idx < g_ring_count) {
uint32_t slot = (g_ring_head + idx) % VT100_SCROLLBACK_MAX_LINES;
uint32_t slot = (g_ring_head + idx) % g_ring_cap;
return g_ring + (size_t)slot * g_line_stride;
} else {
uint32_t row = idx - g_ring_count;
@@ -476,10 +583,12 @@ static const char *scrollback_line_at(uint32_t idx)
/* Redraw every row from the combined history at the current
* g_scroll_offset. Uses the terminal's current default colors, not each
* line's original SGR state -- see this file's 4.4q block. */
* line's original SGR state -- see this file's 4.4q block. `total` now
* spans all four 4.4ac segments, not just the TTF ring + shadow, so
* scrolling back from the REPL reaches all the way through POST. */
static void scrollback_redraw(void)
{
uint32_t total = g_ring_count + g_vt.rows;
uint32_t total = g_boot_ring_count + g_boot_rows + g_ring_count + g_vt.rows;
uint32_t row;
for (row = 0; row < g_vt.rows; row++) {
@@ -534,8 +643,10 @@ void vt100_toggle_graphics(void)
void vt100_scroll_back(uint32_t n)
{
uint32_t history; /* 4.4ac: boot ring + boot shadow + TTF ring, not just the TTF ring */
if (!g_shadow || !g_ring) return;
if (n > g_ring_count - g_scroll_offset) n = g_ring_count - g_scroll_offset;
history = g_boot_ring_count + g_boot_rows + g_ring_count;
if (n > history - g_scroll_offset) n = history - g_scroll_offset;
if (n == 0) return;
g_scroll_offset += n;
scrollback_redraw();
@@ -591,14 +702,14 @@ static void scroll_up(uint32_t lines)
uint32_t i, r;
for (r = 0; r < n; r++) {
uint32_t slot = (g_ring_head + g_ring_count) % VT100_SCROLLBACK_MAX_LINES;
uint32_t slot = (g_ring_head + g_ring_count) % g_ring_cap;
char *dst = g_ring + (size_t)slot * g_line_stride;
char *src = g_shadow + (size_t)r * g_line_stride;
for (i = 0; i < g_line_stride; i++) dst[i] = src[i];
if (g_ring_count < VT100_SCROLLBACK_MAX_LINES) {
if (g_ring_count < g_ring_cap) {
g_ring_count++;
} else {
g_ring_head = (g_ring_head + 1) % VT100_SCROLLBACK_MAX_LINES;
g_ring_head = (g_ring_head + 1) % g_ring_cap;
}
}