FABRIC.md item 4.4x: split the REPL prompt into its own bottom strip

Scope expanded from pure CANVAS-rectangle arithmetic (as originally
scoped) to also splitting the REPL prompt/input line out of the
scrollback box into an independent single-line strip, per Captain Bob's
explicit fold-in after the gap was reported (§25.0 rule 3) rather than
silently expanded.

vt100.c: VT100_BOX_ORIGIN_X/Y are no longer hardcoded per-arch literals --
both are now derived from fb_width()/fb_height() at vt100_enable_ttf()
time. New vt100_strip_draw() renders the bottom strip (gray border lines,
bright-white text) directly via the existing ttf_draw_glyph_cell()
rasterizer, independent of the box's own grid/cursor state. Border lines
are drawn after the glyph loop so an oversized cell can only be clipped
by them, never erase them.

console.c/console.h: console_fb_strip_draw() thin wrapper, matching the
existing console_fb_enable_ttf()/console_fb_scroll_*() pattern.

repl.c: builds a plain-text "[VMName] ok> <input>" mirror in
g_strip_prompt/strip_refresh(), refreshed on every keystroke (including
backspace) from sk_readline() -- already wired for item 4.4v, since
keyboard-typed characters will flow through the same console_getc() path
once that lands. Also widened sk_repl_step()/sk_repl_run()'s local input
buffer from a second, smaller 256-byte buffer to INPUT_BUFFER_SIZE
(1025), per 4.4w's decision.

Verified: three-arch clean QEMU boot + logs, amd64 screendump showing
the box and strip as two visually distinct regions with no visible
glyph/border clipping.

Punch list §25 item 4.4x complete.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-08-12 14:59:07 -04:00
co-authored by Claude Sonnet 5
parent 763c6f2cd5
commit 91742e02f4
21 changed files with 42690 additions and 23 deletions
+7
View File
@@ -317,6 +317,13 @@ void console_fb_scroll_fwd(uint32_t n)
}
}
void console_fb_strip_draw(const char *text)
{
if (fb_is_available()) {
vt100_strip_draw(text);
}
}
/**
* Read a single character from serial console (non-blocking)
* Returns -1 if no character available
+97 -13
View File
@@ -45,6 +45,7 @@
#include "starkernel/kmalloc.h"
#include "log.h"
#include <stdint.h>
#include <string.h>
/* Maximum CSI parameters */
#define VT100_MAX_PARAMS 16
@@ -82,23 +83,40 @@ static ttf_raster_cache_t g_ttf_cache;
/* -----------------------------------------------------------------------
* FABRIC.md item 4.4t: confine REPL text (TTF mode only) to the 640x480
* CANVAS box computed in 4.4o and pixel-verified in 4.4p. Per-arch raster
* top-left origin, from those items' measurements. Boot/POST (bitmap mode)
* is untouched -- g_origin_x/g_origin_y default to (0,0) and only change
* once vt100_enable_ttf() runs.
* CANVAS box computed in 4.4o and pixel-verified in 4.4p. Boot/POST
* (bitmap mode) is untouched -- g_origin_x/g_origin_y default to (0,0)
* and only change once vt100_enable_ttf() runs.
*
* FABRIC.md item 4.4x: origin is now DERIVED from fb_width()/fb_height()
* at vt100_enable_ttf() time rather than hardcoded per-arch literals --
* both the old per-arch numbers (320/104 amd64, 80/4 aarch64+riscv64) and
* the new ones below check out against the same formula, so this is a
* strict generalization, not a behavior change for X. Y changes because
* 4.4u/4.4w replaced 4.4m's 96px/4-line REPL strip with a single-line
* strip pinned to the bottom of the screen (see VT100_STRIP_* below) --
* the box now sits directly above that strip instead of assuming 96px.
* origin_x = (fb_width() - VT100_BOX_W) / 2 -- horizontal center
* origin_y = fb_height() - VT100_BOX_H -- box bottom edge
* - VT100_STRIP_BOX_GAP_PX -- gap above strip
* - (2*VT100_STRIP_GAP_PX + 1) -- strip height
* --------------------------------------------------------------------- */
#if defined(__x86_64__) || defined(__i386__)
#define VT100_BOX_ORIGIN_X 320u
#define VT100_BOX_ORIGIN_Y 104u
#else
#define VT100_BOX_ORIGIN_X 80u
#define VT100_BOX_ORIGIN_Y 4u
#endif
#define VT100_BOX_W 640u
#define VT100_BOX_H 480u
#define VT100_BOX_COLS 53u /* 640 / VT100_TTF_CELL_W_PX, exact */
#define VT100_BOX_ROWS 20u /* 480 / VT100_TTF_CELL_H_PX, exact */
/* -----------------------------------------------------------------------
* FABRIC.md items 4.4u/4.4w/4.4x: the REPL prompt/input strip -- a
* single-line region pinned to the bottom of the screen, independent of
* the scrollback box's own grid (see vt100_strip_draw() below). Numbers
* pinned by Captain Bob directly, 2026-08-12 (4.4w).
* --------------------------------------------------------------------- */
#define VT100_STRIP_GAP_PX 15u /* baseline-to-border-line gap, both sides, mirrored */
#define VT100_STRIP_BOX_GAP_PX 8u /* gap between the strip's top border and the box's bottom edge */
#define VT100_STRIP_BORDER_GRAY FB_RGB(0xAA, 0xAA, 0xAA) /* FB_ANSI_PALETTE[7] */
#define VT100_STRIP_TEXT_WHITE FB_RGB(0xFF, 0xFF, 0xFF)
#define VT100_STRIP_LEFT_MARGIN_PX(fbw) (((fbw) - VT100_BOX_W) / 2u) /* aligned under the box */
static uint32_t g_origin_x = 0; /* box top-left, TTF mode only; 0 in bitmap mode */
static uint32_t g_origin_y = 0;
@@ -268,6 +286,68 @@ static void ttf_draw_glyph_cell(uint32_t cell_px, uint32_t cell_py, uint8_t ch,
}
}
/* FABRIC.md items 4.4u/4.4w/4.4x: draw the REPL prompt/input strip -- a
* single-line region pinned to the bottom of the screen, independent of
* the scrollback box's own grid and cursor state (this function never
* touches g_vt, and nothing in the box's own put_char()/scroll_up() path
* touches the strip). Reuses ttf_draw_glyph_cell() for the actual glyph
* blit -- same rasterizer, same cache, as the box's own grid -- rather
* than a second text-drawing path.
*
* Horizontal scroll (4.4u step 4): @p text is drawn tail-anchored once it
* exceeds the strip's width. That is equivalent to cursor-anchored
* scrolling because the caller (repl.c) only ever appends at, or
* backspaces from, the end of the line -- there is no mid-line cursor
* movement in this REPL, so "keep the cursor visible" and "keep the tail
* visible" are the same window.
*
* Border lines are drawn LAST, after the glyph loop, not first: a glyph
* cell's own opaque background fill (cell_h()=24px) can extend a few
* pixels past the strip's 15px baseline-to-line gap for full-ascender
* characters -- 4.4u's own record already flags these gap numbers as
* "informal... need to be re-checked once actually built." Drawing the
* borders last means an oversized cell can never erase them; it can only
* be visually clipped by them, which is the correct failure direction.
*
* No-op before TTF mode is active. */
void vt100_strip_draw(const char *text)
{
uint32_t fbw, fbh, bottom_line_y, top_line_y, baseline_y, cell_py;
uint32_t margin_x, avail_px, avail_cols, len, start, i;
if (!g_ttf_ready || g_glyph_mode != VT_GLYPH_TTF) return;
if (!text) text = "";
fbw = fb_width();
fbh = fb_height();
bottom_line_y = fbh - 1u;
top_line_y = bottom_line_y - (2u * VT100_STRIP_GAP_PX);
baseline_y = bottom_line_y - VT100_STRIP_GAP_PX;
cell_py = baseline_y - VT100_TTF_SIZE_PX;
margin_x = VT100_STRIP_LEFT_MARGIN_PX(fbw);
/* Clear the whole strip first -- both border lines get redrawn every
* call regardless, so clearing border-to-border is simpler than
* clearing just the interior and no less correct. */
fb_fill_rect(0, top_line_y, fbw, bottom_line_y - top_line_y + 1u, VT100_DEFAULT_BG);
avail_px = (fbw > 2u * margin_x) ? (fbw - 2u * margin_x) : 0u;
avail_cols = cell_w() ? (avail_px / cell_w()) : 0u;
len = (uint32_t)strlen(text);
start = (len > avail_cols) ? (len - avail_cols) : 0u;
for (i = start; i < len; i++) {
uint32_t col = i - start;
ttf_draw_glyph_cell(margin_x + col * cell_w(), cell_py,
(uint8_t)text[i],
VT100_STRIP_TEXT_WHITE, VT100_DEFAULT_BG);
}
fb_fill_rect(0, top_line_y, fbw, 1u, VT100_STRIP_BORDER_GRAY);
fb_fill_rect(0, bottom_line_y, fbw, 1u, VT100_STRIP_BORDER_GRAY);
}
static void draw_cursor_glyph(uint8_t ch)
{
uint32_t f = eff_fg();
@@ -391,8 +471,12 @@ void vt100_enable_ttf(void)
* scrollback allocation block just below depends on g_vt.cols via
* g_line_stride, so origin/cols/rows MUST be set first or every
* shadow/ring index silently corrupts instead of crashing. */
g_origin_x = VT100_BOX_ORIGIN_X;
g_origin_y = VT100_BOX_ORIGIN_Y;
/* FABRIC.md item 4.4x: derived from the real framebuffer size, not a
* hardcoded per-arch literal -- see the formula in this file's header
* comment above VT100_BOX_W. */
g_origin_x = VT100_STRIP_LEFT_MARGIN_PX(fb_width());
g_origin_y = fb_height() - VT100_BOX_H - VT100_STRIP_BOX_GAP_PX
- (2u * VT100_STRIP_GAP_PX + 1u);
g_vt.cols = VT100_BOX_COLS;
g_vt.rows = VT100_BOX_ROWS;
g_vt.cx = g_vt.cy = 0;