FABRIC.md item 4.4t: confine REPL text rendering to the CANVAS box

vt100's TTF-mode text grid now operates within the per-arch 640x480 box
(computed in 4.4o, pixel-verified in 4.4p) instead of the full framebuffer:
box-origin offset in px_of()/py_of(), box-derived cols/rows (53x20) set
before the 4.4q scrollback allocation depends on them, mode-aware
erase_display()/reverse-index fill, and a new box-scoped fb_scroll_rect()
alongside the existing whole-framebuffer fb_scroll_rows() (bitmap/boot mode
unaffected either way). Also clears the full framebuffer once at the
bitmap-to-TTF switch so leftover boot debris doesn't sit frozen outside the
box now that erase_display(2) is box-scoped afterward.

Three-arch QEMU boot + pixel-scanned screendumps confirm zero non-background
pixels land outside the box on amd64, aarch64, and riscv64.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-08-11 23:07:04 -04:00
co-authored by Claude Sonnet 5
parent 1f3ec3554e
commit c92be2768f
21 changed files with 91586 additions and 112 deletions
+53
View File
@@ -305,3 +305,56 @@ void fb_scroll_rows(uint32_t char_rows, uint32_t bg)
}
}
}
/**
* fb_scroll_rect — box-confined scroll (FABRIC.md item 4.4t).
*
* Same non-volatile bulk-copy rationale as fb_scroll_rows() above, but
* bounded to a caller-supplied rectangle instead of the whole framebuffer --
* needed once REPL text is confined to the 4.4o CANVAS box, since a
* whole-framebuffer scroll would drag pixels from outside the box (e.g. a
* future 4.4r border) into the text area. Takes pixel_rows directly rather
* than char_rows * a hardcoded cell height, since TTF mode's cell height
* (24px) differs from bitmap mode's (16px * scale).
*/
void fb_scroll_rect(uint32_t x, uint32_t y, uint32_t w, uint32_t h,
uint32_t pixel_rows, uint32_t bg)
{
uint32_t src_y, dst_y, px;
uint32_t packed_bg;
uint32_t *fb = (uint32_t *)(uintptr_t)g_fb.base;
uint32_t x_end, y_end;
if (!g_fb.ready || pixel_rows == 0 || w == 0 || h == 0) return;
x_end = x + w;
y_end = y + h;
if (x_end > g_fb.width) x_end = g_fb.width;
if (y_end > g_fb.height) y_end = g_fb.height;
if (x >= x_end || y >= y_end) return;
if (pixel_rows >= (y_end - y)) {
fb_fill_rect(x, y, x_end - x, y_end - y, bg);
return;
}
packed_bg = pack_pixel(bg);
/* Copy pixel rows upward, bounded to [x, x_end) */
for (dst_y = y; dst_y + pixel_rows < y_end; dst_y++) {
src_y = dst_y + pixel_rows;
uint32_t *src = fb + src_y * g_fb.stride + x;
uint32_t *dst = fb + dst_y * g_fb.stride + x;
for (px = 0; px < (x_end - x); px++) {
dst[px] = src[px];
}
}
/* Clear the newly exposed rows at the bottom of the rect */
for (dst_y = y_end - pixel_rows; dst_y < y_end; dst_y++) {
uint32_t *row = fb + dst_y * g_fb.stride + x;
for (px = 0; px < (x_end - x); px++) {
row[px] = packed_bg;
}
}
}