starkernel: item 4.3.3 -- Cartesian coordinate machinery, found and fixed a VARIABLE alignment bug

Adds Module 28 (framebuffer_words.c/.h): PLOT ( x y color -- ), FB-WIDTH,
FB-HEIGHT -- raw hardware-boundary C primitives, kernel-only, no-op on
hosted builds, same pattern as every other module.

Adds capsules/fabric.4th (blocks 4900-4902, mkcapsule --lint clean):
COS45/Z->DELTA/PROJECT/CART-Y/CART-PLOT -- the 45-degree cavalier
orthographic projection and Y-flip, in FORTH per the compose-in-FORTH-first
rule (this is policy, not hardware access).

Found and fixed a second real bug while live-testing CART-PLOT over the
serial socket: defining_word_variable() (defining_words.c) captured
vm->here as a VARIABLE's address with no alignment call first, while
vm_load_cell/vm_store_cell require 8-byte-aligned addresses. This capsule's
VARIABLE ZD landed misaligned (945) purely by chance of what preceded it;
other capsules' variables happened to land aligned by luck, not guarantee.
Real deviation from FORTH-83/ANS, which specifies VARIABLE reserves an
aligned cell. Fixed with vm_align(vm) before capturing addr -- ALIGN
already existed as a word but VARIABLE wasn't calling it.

Verified end-to-end on amd64 via manual serial injection + QEMU screendump:
plotted 4 marker points (origin, +100 X, +100 Y, +50 Z) and confirmed all
landed at hand-calculated raster coordinates, including the diagonal
up-right shift for the Z-axis point -- the projection math is correct, not
just non-crashing. fb/fabric-test-cart-plot.png. 4.3.1's corner diagnostic
still renders correctly in the same shot, confirming no regression.

All three architectures (amd64/aarch64/riscv64) boot clean to ok> with the
DoE completing; dict_hash identical across all three
(0xc7f9adf885e306d2), confirming parity is unaffected.

FABRIC.md item 4.3.3 marked done with full acceptance evidence.
This commit is contained in:
Robert Allan James
2026-08-07 13:20:52 -04:00
parent fbf0625317
commit ef9806977a
9 changed files with 173 additions and 3 deletions
+1
View File
@@ -478,6 +478,7 @@ static void defining_word_variable(VM *vm) {
return;
}
vm_align(vm);
vaddr_t addr = (vaddr_t) vm->here;
void *p = vm_allot(vm, sizeof(cell_t));
if (!p) {
+65
View File
@@ -0,0 +1,65 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 20232025 Robert A. James
All rights reserved.
Licensed under the StarForth License, Version 1.0
*/
/* framebuffer_words.c — raw framebuffer hardware-boundary FORTH words
* (FABRIC.md item 4.3.3). Kernel-only; no-op on hosted builds. */
#include "include/framebuffer_words.h"
#include "../../include/log.h"
#include "../../include/word_registry.h"
#ifdef __STARKERNEL__
#include "starkernel/framebuffer.h"
#endif
/* PLOT ( x y color -- ) : raw raster pixel write, top-left origin, Y-down.
* No Cartesian awareness -- that is capsules/fabric.4th's job. */
static void fbw_plot(VM *vm)
{
if (vm->dsp < 2) {
log_message(LOG_ERROR, "PLOT: Stack underflow");
vm->error = 1;
return;
}
cell_t color = vm_pop(vm);
cell_t y = vm_pop(vm);
cell_t x = vm_pop(vm);
#ifdef __STARKERNEL__
fb_put_pixel((uint32_t)x, (uint32_t)y, (uint32_t)color);
#else
(void)x; (void)y; (void)color;
#endif
}
/* FB-WIDTH ( -- n ) */
static void fbw_width(VM *vm)
{
#ifdef __STARKERNEL__
vm_push(vm, (cell_t)fb_width());
#else
vm_push(vm, 0);
#endif
}
/* FB-HEIGHT ( -- n ) */
static void fbw_height(VM *vm)
{
#ifdef __STARKERNEL__
vm_push(vm, (cell_t)fb_height());
#else
vm_push(vm, 0);
#endif
}
void register_framebuffer_words(VM *vm)
{
register_word(vm, "PLOT", fbw_plot);
register_word(vm, "FB-WIDTH", fbw_width);
register_word(vm, "FB-HEIGHT", fbw_height);
}
@@ -0,0 +1,38 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 20232025 Robert A. James
All rights reserved.
Licensed under the StarForth License, Version 1.0
*/
#ifndef FRAMEBUFFER_WORDS_H
#define FRAMEBUFFER_WORDS_H
#include "vm.h"
/**
* @defgroup framebuffer_words Framebuffer Words
* @{
*
* @brief Raw hardware-boundary FORTH words for the Console drawing fabric
* (FABRIC.md item 4.3.3). Deliberately raster-native -- no Cartesian
* awareness, no policy. That lives in capsules/fabric.4th instead, per the
* compose-in-FORTH-first rule.
*
* Kernel-only: no-op on hosted builds (no framebuffer exists there).
*
* @par PLOT ( x y color -- )
* Write one pixel at raster coordinates (top-left origin, Y-down).
* Out-of-bounds writes are silently ignored (inherited from fb_put_pixel).
*
* @par FB-WIDTH ( -- n )
* @par FB-HEIGHT ( -- n )
* Push the framebuffer's pixel width / height.
* @}
*/
void register_framebuffer_words(VM *vm);
#endif /* FRAMEBUFFER_WORDS_H */