Default log level dropped from info to warn so the per-word ECW dispatch
trace doesn't flood REPL output after POST (--log-level=info/debug still
re-enables it). qemu target gains QEMU_DISPLAY (default gtk) so the
framebuffer window shows by default; serial log is tee'd live via
`tail -f` instead of dumped with `cat` at the end. Includes regenerated
BLOCK_MAP.md/amd64.csv/artemis.img and this morning's boot logs/DoE runs
from the sessions that produced this WIP.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
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>
Verification only, no permanent code (matches this item's own title
and 4.4i's same-shape precedent -- 4.4l-4.4o were geometry decisions,
not drawing code). One-shot diagnostic probe in sk_repl() drew 4.4o's
box outline plus a strip-top marker, then was reverted after capture
per this document's write/run-once/capture/revert discipline.
Measured pixel bounds directly from each screendump (not eyeballed)
and confirmed exact matches against 4.4o's computed coordinates on
all three architectures: amd64 box x:[320,959] y:[104,583], strip
marker y:704; aarch64/riscv64 box x:[80,719] y:[4,483], strip marker
y:504.
Observed, not fixed here: on the small architectures the REPL banner
text visibly overlaps the box's top edge, because vt100's cursor grid
still spans the whole screen rather than being confined to the 96px
strip -- a real gap between the landed REPL path and the mockup,
belongs to 4.4q/4.4r's wiring work, not this item.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
No code changes -- investigation/verification only. Injected a raw SGR
escape sequence over the serial socket at the ok> prompt (composed from
EMIT + ." since no FORTH word emits a literal ESC byte + text
directly): 27 EMIT ." [95mCOLOR-TEST" 27 EMIT ." [39m", bright magenta
(the p>=90&&p<=97 branch in apply_sgr()), a third SGR code path
distinct from 4.4h's already-verified truecolor prompt.
Screendump on all three architectures confirms COLOR-TEST renders in
bright magenta via the 4.4j-retargeted TTF draw call, proving the SGR
parser and the new glyph backend work together end-to-end, not just
independently. First screendump evidence ever captured for
aarch64/riscv64 in this document -- every prior screendump item was
amd64-only with that gap explicitly accepted; incidentally closed here
via the QEMU monitor screendump command against each arch's own
display device (ramfb for aarch64/riscv64). Both confirmed booting at
800x600.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
font_8x16.c keeps rendering everything through and including POST;
TTF-TEXT's rasterizer takes over at the interactive REPL boundary
(sk_repl()) via a new runtime mode switch, vt100_enable_ttf()
(console_fb_enable_ttf() wrapper), not a compile-time swap -- both
backends coexist in the same binary since boot/POST must stay
font_8x16.c per this item's own done-when.
TTF-TEXT (the FORTH word) isn't directly callable from vt100.c -- VM
stack arguments, different call shape than a one-glyph cell draw. Used
hal/ttf.c's VM-independent primitives directly instead (same rasterizer
TTF-TEXT itself calls underneath), added as a native C helper in
vt100.c. Lazily loads fonts:JetBrainsMono-Regular.ttf and kmallocs a
96-slot raster cache (covers all 95 printable ASCII, no eviction
thrash) on first switch.
Cell geometry changes at the switch (mode-aware cell_w()/cell_h()):
provisional 12x24 TTF cell (600/1000em * 20px = 12px exactly, using
4.4i's confirmed-uniform hmtx advance width) vs font_8x16's fixed 8x16
-- cols/rows re-derived and screen cleared at the switch point, same as
vt100_init() itself does. Final REPL text size is 4.4m's decision, not
this item's.
Also fixes the second call site 4.4i flagged: erase_line_range() now
uses one fb_fill_rect() instead of a per-cell font_8x16-specific blank
glyph draw, consistent with erase_display(2)'s full-screen case.
Three-arch verified: amd64/aarch64/riscv64 all reach ok>, POST
Failed: 0, identical dict-hashes. amd64 screendump shows real
proportional JetBrains Mono letterforms on the REPL tail, visibly
distinct from every prior font_8x16 screenshot.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
console.c's emit_prefix() now wraps [VMName] (brackets included) in
FABRIC.md 4.4's locked orange (0xFFA500), and repl.c's two "ok> "
call sites send FABRIC.md 4.4's locked cyan (0x55FFFF), both as real
SGR escape sequences through the existing font_8x16.c/vt100.c pipeline
-- 4.4b already established this needs no dependency on TTF-TEXT/4.4j.
Sent through both raw_putc() (serial) and vt100_putc() (framebuffer),
matching the existing dual-path pattern, so an ANSI-aware serial
terminal renders the same colors as the framebuffer.
Three-arch verified: amd64/aarch64/riscv64 all reach ok>, POST
Failed: 0, identical dict-hashes. Color applies correctly to any VM
name (confirmed via the [Hermes]-prefixed PARITY:BIRTH line in all
three logs, not just [Hera]). amd64 screendump confirms the rendered
colors directly.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Moves the console_fb_init() call in kernel_main.c from after
capsule_birth_mama() to before it, so the fleet-birth/self-test
transcript (Hermes x2, Artemis births, Stadium self-tests -- currently
serial-only) is also framebuffer-visible, not just the small post-birth
tail.
4.5f's -O2 experiment already showed this doesn't hang under
optimization, just costs roughly 12x more boot-time heartbeat ticks
(one-shot, paid only during fleet birth, never repeated at runtime).
Captain Bob's call: worth it, since the serial log was never the
problem -- this is about the same transcript also reaching a real
screen.
Three-arch verified: amd64/aarch64/riscv64 all reach ok>, POST
Failed: 0, identical dict-hashes across all three. amd64 screendump
confirms the framebuffer now carries the full transcript.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Uncommitted experiment (code reverted after capture, per Captain Bob):
moved console_fb_init() before capsule_birth_mama() in kernel_main.c,
amd64 only. At -O2 the boot completes cleanly and reaches ok> well
inside a 300s bound, versus the indefinite stall previously seen at
-O0. Real cost: ~12x more heartbeat ticks during boot from the extra
framebuffer scroll volume -- not free, but not a hang.
Screendump confirms the actual point of 4.4g: the framebuffer now
carries the full HADES/ECW/Stadium/self-test transcript, not just the
small post-birth tail.
This informs 4.4g's open reorder decision, it doesn't make it -- 4.5f
stays unchecked (single-arch feasibility check only, not the three-arch
acceptance pass its own done-when requires).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
FABRIC.md item 4.5d Finding 4: lidt()'s inline asm used a register-only
("r") constraint on the idtr pointer, never telling GCC the asm
dereferences the pointee. At -O2 this let the compiler treat the
256-entry idt[] population loop and idtr_desc's field writes as dead
stores and eliminate them entirely, loading IDTR from uninitialized
stack instead of the real table -- a #GP on the first APIC timer tick
that happened to land on garbage. Same bug class as the earlier
muldiv64 fix (b43e51a): an inline-asm constraint too weak for what the
asm actually touches, invisible at -O0, live at -O2.
Fixed by switching to a memory operand ("m"(*idtr_desc)), matching how
Linux's own load_idt() is written. aarch64/riscv64 checked for the same
pattern -- neither has it, both install their vector/trap tables
entirely in hand-written .S.
Verified: all three architectures boot clean to ok>, POST Failed: 0,
identical dict-hashes across all three under -O2, zero new warnings
vs an -O0 baseline (amd64 3040/3040, aarch64 3041/3041 serial,
riscv64 3037/3037). -O2/-U_FORTIFY_SOURCE landed permanently in
COMMON_CFLAGS.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Computed the loader's true runtime relocation delta to correctly correlate
fault addresses (the running image is starkernel_loader.efi, a relocated
PE, not the separately-linked starkernel_kernel.elf assumed at first).
Fault RIP decodes to log_message()'s entry -- coincidental, not causal,
since it's called on nearly every HADES dispatch during word registration.
Used QEMU's monitor for -d exec,int tracing. Late-start tracing (stop right
before the danger zone to keep trace size down) failed twice -- the window
between a detectable checkpoint and the crash is shorter than host-side
reaction latency. Fell back to full-boot tracing from -S (~2.7GB per
attempt, not committed). That trace shows an unremarkable, normal-looking
repeating three-block loop immediately before the fault, then "Servicing
hardware INT=0x20" (APIC_TIMER_VECTOR) with IDT already showing limit=0 at
that instant.
Ruled out a second illegitimate lidt call (only one call site exists
anywhere, one-time M4 boot setup; searched the trace for any later
execution of that address range and found none). Not yet established: the
actual corrupting write. Documented two remaining explanations (earlier
silent corruption vs. a genuine TCG artifact) and that pinpointing the
exact instruction needs GDB-level single-stepping, a bigger tooling step
than attempted this session.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Used QEMU-side instrumentation (-d int tracing, plus a working chardev-based
monitor -- the older bareword -monitor syntax silently fails on QEMU
10.2.1) as directed. The PM Timer "stall" was never a hang: it was a #DE
divide error cascading to a triple fault, which -no-reboot converts into a
silent clean QEMU exit -- indistinguishable from a hang without tracing,
and why arch_relax() (solving a hang that didn't exist) had no effect.
Root cause and fix documented in full (also see commit b43e51a).
With the fix, amd64 boot at -O2 now proceeds far past the original stall --
through capsule birth and into Hermes's word registration -- before hitting
a second, different fault (Finding 4): a direct #GP at IDT index 32
(APIC_TIMER_VECTOR), not yet root-caused. Same silent-triple-fault-exit
shape, so this was very likely bundled into "the hang" before tracing
distinguished the two separate bugs.
-O2 reverted again (uncommitted); muldiv64's fix is kept, real and
independently verified at unchanged -O0 on all three architectures.
Routine three-arch artifacts from this session's verification runs
included.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Follow-up probing narrowed Finding 3's behavior (inconsistent stall point
run-to-run: sometimes reaches iters=1000/delta=951 before stopping,
sometimes never gets past iters=0 even after a 400-second bounded wait) but
didn't pin the mechanism.
Tested the strongest available hypothesis: single-threaded TCG scheduling
starvation from an -O2-tightened spin loop, based on a real precedent --
calibrate_apic_timer() (apic.c) already calls arch_relax() every iteration
of its own spin-wait; calibrate_tsc_with_pmtimer() never had it. Added the
same call, matching that precedent exactly. Result: no change, same exact
stall point on a fresh 60-second bounded wait. Reverted.
Stopping here per this document's own §25.0 rule 5 -- root-causing further
needs either deeper TCG/QEMU internals knowledge or a different diagnostic
approach (host-side instrumentation) than another guess-and-check pass.
Makefile.starkernel and timer.c both back to committed -O0 state, confirmed
booting clean.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Finding 2 (putc/getc) fixed via shim.c backfill (previous commit). With
both Finding 1 (-U_FORTIFY_SOURCE) and Finding 2 fixed, amd64 links clean
at -O2 -- rigorously confirmed zero new warnings by diffing normalized
warning text between -O0 and -O2 builds (empty diff, ~3040 pre-existing
warnings in vendored code unchanged).
New Finding 3, not fixed, now blocking: amd64 boot stalls for minutes
inside calibrate_tsc_with_pmtimer() (timer.c:560-589) at -O2 -- a
mainline busy-wait loop reading the ACPI PM Timer via genuinely-volatile
inl() until 1000 real ticks elapse, bounded by a 5M-iteration timeout. A
one-shot diagnostic probe (reverted after capture) caught exactly one
checkpoint in 55 seconds of observation, then nothing -- not yet
root-caused whether this is the 5M-iteration timeout itself taking several
minutes to exhaust under -O2, or a genuine non-terminating condition.
Makefile.starkernel reverted to -O0 again; nothing broken landed in
history. Routine artifacts from this session's boot attempts included.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Marks 4.5b done with the verification record (three log dirs, all reaching
[Hera] ok> at unchanged -O0). Routine artifacts from this session's
three-arch runs: capsules/BLOCK_MAP.md, disk/artemis.img, DOE CSVs, QEMU
serial logs.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Attempted 4.4g's console_fb_init() reorder twice this session: once bare,
once with the fb_scroll_rows() volatile fix applied. Both stalled boot
indefinitely (12,000+ lines logged, still running after 3 minutes vs. a
normal few-second boot) instead of completing. Root cause traced past the
scroll fix to Makefile.starkernel building the kernel at -O0 -- no
optimization flag has ever been configured there, verified against the
file's full git history (17 commits, only ever one unrelated host-tool -O2
line). Both the kernel's own vendored hosted Makefile and the standalone
StarForth repo's Makefile default to -O2 (up to -O3/-flto on faster
targets); Makefile.starkernel was written fresh for the bare-metal target
and never got that ladder.
Documented as new item 4.6: enabling optimization is not a safe drop-in
change on its own. Found one confirmed, isolated correctness hazard first --
TimeTrustState.ticks (timer.h:90) is written directly in ISR context on all
three architectures (heartbeat.c:163) and read directly by mainline
(heartbeat.c:200-202, including a busy-wait in kernel_main.c:880) without
being volatile, unlike every other ISR-shared global checked
(g_spurious_count, g_plic_claim_count, g_pending_counter/g_pending_valid/
g_adaptive_period_ns are all correctly volatile already). Reverted the
console_fb_init() reorder itself (uncommitted, so a plain git restore) --
4.4g stays open pending 4.6.
Both the reorder attempts' logs (stalled, never reached ok>) and this
session's routine three-arch artifacts (capsules/BLOCK_MAP.md, disk/
artemis.img, DOE CSV) are committed as audit trail per CLAUDE.md.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Three-arch acceptance boot (amd64/aarch64/riscv64) clean, all reaching
[Hera] ok>, plus amd64 screendump evidence for each item -- checking off
4.4c (console_fb_init wiring), 4.4d ([VMName] prefix reaching the
framebuffer), 4.4e (\r/\n fix), and 4.4f (glyph-flip fix, previous commit).
Screendump verification itself stays amd64-only pending future
aarch64/riscv64 screendump tooling; Captain Bob accepted that gap as
non-blocking for these checkboxes.
4.4g reframed from "anomaly, not root-caused" to a design decision: traced
the missing hundreds of lines of boot output to console_fb_init() running
after capsule_birth_mama() (which births the whole Tripod fleet and their
self-tests) rather than before it, so that output is serial-only by
construction -- not a scroll/CSI-parser bug. Left unchecked pending a call
on whether to reorder console_fb_init() earlier in boot.
Also commits routine artifacts from this session's three-arch runs:
capsules/BLOCK_MAP.md (regenerated manifest), disk/artemis.img (Artemis
writes on every boot), DOE CSVs, and the corresponding QEMU serial logs.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Punch list §25 item 4.4a complete.
Removed the manually-built <Name>)ok>/zuse)ok> prompt suffix from
sk_repl_step()/sk_repl_run() -- console.c's console_putc() already emits a
"[VMName] " prefix at line-start, so the old code was double-printing the
name for non-Hera VMs ("[Hermes] Hermes)ok> "). Now prints only "ok> " and
lets the existing prefix supply the bracket. emergency_console/zuse_session
security semantics unchanged, display-only. Verified: all three
architectures boot live to "[Hera] ok>" (logs/20260811-073408 amd64,
logs/20260811-073448 aarch64, logs/20260811-073542 riscv64).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Punch list §25 item 4.3.7f complete. No code changes -- ttf.c/ttf.h/
ttf_words.c unchanged since 4.3.7e; this is verification only, same
posture as 4.3.6g.
Three-arch acceptance boot clean (standard make ARCH=<arch> clean qemu,
run in order, one at a time): all three reached [Hera] ok> cleanly,
logs committed under logs/20260810-231553(amd64)/231640(aarch64)/
231739(riscv64).
One screendump per architecture, identical TTF-TEXT command sequence
(S" Hi 4.3.7e!" 200 200 28 16777215 TTF-TEXT) via monitor-socket +
serial-injection, same pattern as 4.3.7c/e. All three show the string
rendered legibly with correct mixed-case/digit/punctuation glyphs and
identical monospace spacing.
TrueType rendering (4.3.7-4.3.7f) is now complete, adjunct to the
stroke font per §27.7 decision #4. Stopping here before scoping REPL
wiring (M8), per this item's own posture matching 4.3.6g.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Punch list §25 item 4.3.6g complete. Fixed the cross-arch bug found
while first attempting this checkpoint: aarch64 and riscv64 both boot
an 800x600 ramfb device (confirmed empirically for both, not assumed),
against amd64's 1280x800 GOP framebuffer -- the original verification
grid's Y coordinates ran off the top of the smaller screens. Rebuilt
the grid to fit 800x600, safe on amd64's larger screen too.
All 113 glyphs confirmed legible and correctly positioned on all three
architectures, one screendump each, from the identical FORTH command
sequence. No code changes -- font.4th/fabric.4th are unchanged since
4.3.6f; this is verification only.
Three-arch acceptance boot clean, Stadium conservation unchanged.
Stroke font (4.3.6-4.3.6g) is now complete. Per this item's own
posture, stopping here before scoping REPL wiring (M8) -- 4.3.7
(TrueType, adjunct) is the scoped-but-not-started next work.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Punch list §25 item 4.3.6f complete. TEXT ( c-addr u x y size color -- )
walks a UTF-8 byte string, decoding one codepoint at a time via
DECODE-UTF8, drawing each via DRAW-GLYPH, and accumulating the cursor
X by the glyph's em-advance scaled to pixels. Uses 2>R/2R> to stash
the DECODE-UTF8 remainder off the data stack while DRAW-GLYPH's args
are pushed.
Defined in font.4th, not fabric.4th, so its compiled DRAW-GLYPH call
binds to the real definition, not fabric.4th's 4.3.6b placeholder --
same early-binding constraint as DISPATCH-GLYPH itself.
Verified live: a string mixing ASCII, Latin-1 (degree sign), and
General Punctuation (em dash) renders correctly in one TEXT call with
proportional spacing, no overlap.
Three-arch acceptance boot clean, Stadium conservation unchanged.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Punch list §25 item 4.3.6e complete. Optional capsule, not wired into
the boot chain, overriding DISPATCH-UPPER's codepoint 65 (G-A) with a
marked alternate glyph while every other letter falls through to
font.4th's originals unchanged.
Corrects §27.6.5: redefining the DISPATCH-* bucket word alone is not
enough, since DISPATCH-GLYPH/DRAW-GLYPH are themselves compiled early
against font.4th's own dispatch words (same early-binding finding as
4.3.6c, one layer up). Verified both ways: the partial override (bucket
word only) left DRAW-GLYPH still drawing the original glyph; redefining
DISPATCH-GLYPH/DRAW-GLYPH too made the override take effect.
Three-arch acceptance boot clean, Stadium conservation unchanged.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Punch list §25 item 4.3.6d complete. Both capsules now EXEC via
Hera's Mama IDENTITY capsule (init.4th block 2049), loading on every
normal boot instead of manual serial injection. Verified by booting
with zero injected commands and calling DRAW-GLYPH directly.
Three-arch acceptance boot clean, Stadium conservation unchanged.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Punch list §25 item 4.3.6c complete. Digits, upper/lowercase, ASCII
punctuation, Latin-1 Supplement, and General Punctuation, each a
stroke-drawing word in the 4.3.6 em-square convention.
Corrected mid-build: the first pass used G-LINE only, missing that
CIRCLE/ARC/ELLIPSE already existed (4.3.3b). Reworked all round shapes
to use them via new em-square-aware wrappers (G-CIRCLE/G-ARC/G-ELLIPSE/
EM-R, fabric.4th 5000-5002), bounded to quarter/half/three-quarter/
full sweeps to avoid unbounded per-glyph angle tuning. font.4th also
redefines DISPATCH-GLYPH/DRAW-GLYPH themselves, not just the six
DISPATCH-* bucket words, since fabric.4th's originals were compiled
early against the 4.3.6b placeholders (CASE early-binding, §27.6.5).
Three-arch acceptance boot clean, Stadium conservation unchanged.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Punch list §25 item 4.3.6b complete.
capsules/fabric.4th blocks 4921-4924: DISPATCH-GLYPH routing via
WITHIN to six bucket words (DISPATCH-DIGIT/-UPPER/-LOWER/
-ASCII-PUNCT/-LATIN1/-GENPUNCT), TOFU fallback, DRAW-GLYPH. Buckets
carry one placeholder stroke word each (G-TEST-*), not the real
113-glyph set -- that's item 4.3.6c's scope, deliberately deferred.
Also: merged two lines in block 4920 (DECODE-UTF8) to fit mkcapsule's
real 64-char x 16-line block limit once a trailing blank separator
line is counted against it -- mechanical reformat, re-verified via a
DECODE-UTF8 regression check (65/176/8212, unchanged).
Verified live on amd64: one representative codepoint per bucket plus
one out-of-range codepoint, all seven DRAW-GLYPH results matched
expected exactly (400/600/450/250/550/700/500-TOFU). Three-arch
acceptance boot clean, Stadium conservation unaffected.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Punch list §25 item 4.3.6a complete.
capsules/fabric.4th blocks 4918-4920: UTF8-SEQ-LEN/UTF8-CONT? verbatim
from §27.6.2, DECODE-UTF8 dispatching to four UTF8-ASSEMBLE-N helpers
(1/2/3/4-byte). Factored into helpers after the single-word draft hit
mkcapsule's real 64-char x 16-line block limit. Verified live on
amd64: a 6-byte test buffer (ASCII + deg-sign + em-dash) decoded to
65/176/8212 across three sequential calls, exact match against
hand-computed codepoints, buffer fully consumed. Three-arch acceptance
boot clean, Stadium conservation unaffected.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Punch list §25 item 4.3.6 complete.
capsules/fabric.4th blocks 4916-4917: EM-UNITS, GOX/GOY/GSIZE/GCOLOR,
EM-X/EM-Y/G-LINE per §27.6.1, scaling/translating em-square strokes
into CART-PLOT screen coordinates via */. Verified live on amd64 via
a temporary probe (built, run once, reverted): two G-LINE test shapes
at GSIZE 100 and GSIZE 50, one leg each exercising a negative em-y
value chosen to hit */'s truncate-toward-zero behaviour, not a
multiple of EM-UNITS. Screendump pixel-bbox extraction matched
hand-calculated raster coordinates exactly on both shapes. Three-arch
acceptance boot clean, Stadium conservation unaffected
(resident_sum=43691 reservoir=21845 sum=65536 on all three).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Punch list §25 item 4.3.5f complete. Keyboard-input slice done.
New KEY-EVENT ( -- keycode pressed -1 | 0 ) converges amd64's i8042,
riscv64's and aarch64's virtio-keyboard-pci behind one shape. Translation
needed is minimal: XT Set-1 make codes and Linux input keycodes are
numerically identical across the standard 84-key block (a documented
historical property of the Linux input subsystem, confirmed against this
host's own headers and this tree's own prior live observations), so amd64
needs two lines, riscv64/aarch64 need none. Verified live with a real
keypress on all three architectures: identical "-1 1 30" output everywhere.
Three-arch acceptance boot clean.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Punch list §25 item 4.3.5e complete.
Extended virtio_input.c with a GIC-routed interrupt path alongside 4.3.5c's
PLIC one -- same capability walk, feature negotiation, eventq handling
(confirming §27.5.1's prediction that these items would share most of the
driver). aarch64_irq_handler() dispatches to virtio_input_isr() before its
EOIR write, same claim-dispatch-complete ordering riscv64 uses. Verified
with a real QEMU sendkey keypress: exact KEY_A/press match, two real
interrupts serviced, identical result to riscv64. Found (not fixed) an
unrelated pre-existing bug: BYE's cold-reset path faults on aarch64,
discovered incidentally since nobody had exercised it from a monitored
session before. Three-arch acceptance boot clean.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Punch list §25 item 4.3.5d complete.
New apic_spi_enable(intid) generalizes item 0.6's PPI-only sequence to one
explicit SPI (IPRIORITYR/ISENABLER/ITARGETSR, ICFGR read-checked not
written). Verified via a software-pended SPI (GICD_ISPENDR, no device
needed) through the existing generic IRQ dispatch, which needed no changes.
Found and fixed a real bug during verification: PSTATE.I is still set at
apic_init()'s point in boot, so the first self-test run correctly latched
but never took the interrupt. Self-test code reverted after capturing
evidence -- interrupts.c has zero net diff, only apic_spi_enable() remains,
unused until 4.3.5e. Three-arch acceptance boot clean.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Punch list §25 item 4.3.5c complete.
Amended from a nonexistent MMIO transport to PCI (matching the board's
actual virtio-blk-pci precedent). New virtio-input driver: eventq with
pre-posted buffers, PLIC source computed at runtime from PCI slot/pin
(derived live from this host's QEMU riscv64 DTB), mandatory ISR-status
read, PCI interrupt-disable-bit check. New VKBD-EVENT/VKBD-DEBUG FORTH
words. Verified with a real QEMU sendkey keypress: exact KEY_A/press
match, two real interrupts serviced, zero exceptions. Three-arch
acceptance boot clean.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Punch list §25 item 4.3.5b complete.
sie.SEIE enabled, PLIC threshold/claim/complete wired into the trap
handler. Verified with a UART-loopback synthetic interrupt (PLIC has no
software set-pending register, unlike GICv2): claim_count=1, last_irq=10,
IIR confirms genuine receive-data cause, byte matched exactly. Self-test
code run once for evidence then fully reverted, per Captain Bob's ruling;
only the permanent substrate remains, no source enabled by default.
Three-arch acceptance boot clean, zero exceptions.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Punch list §25 item 4.3.5a complete.
Firmware (EDK2 RISC-V) leaves Sv57 paging active at kernel entry with an
identity mapping that has at least one confirmed hole (PLIC threshold
register). Verified live before acting: satp.MODE/PPN and __kernel_start's
address confirmed identity mapping; switched to Bare mode (csrw satp, x0 +
sfence.vma) in arch_early_init(), ahead of pmm_init()/vmm_init().
Three-arch acceptance boot clean, zero exceptions; riscv64's PLIC_THRESHOLD
write now survives (PLIC driver code itself lands separately with 4.3.5b).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Live-probed GICD_ITARGETSR before starting 4.3.5d/e; survived cleanly,
unlike riscv64's PLIC (4.3.5a). Decoded QEMU's own virt DTB for the PCI
slot->SPI interrupt-map routing table and closed-form formula. Amended
4.3.5d's circular acceptance criterion to a synthetic GICD_ISPENDR probe
(mirrors 4.3.5b's fix for the same problem), and wrote the full 4.3.5e
virtio-input execution plan: device ID 0x1052, the real virtio_input_event
struct, the mandatory ISR-status read, and the pci_enable() interrupt-
disable-bit gap.
Log: logs/20260808-093228/aarch64/.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Three-arch acceptance run (amd64/aarch64/riscv64) after adding -device
ramfb to aarch64/riscv64, plus the interactive GOP-verification and cube
sessions on both.
Three-arch acceptance run (amd64/aarch64/riscv64) plus the interactive
sessions used to test LINE/CIRCLE/ARC/ELLIPSE, the block-span test, and
verify the Q.TO-INT fix.
Three-arch acceptance run (amd64/aarch64/riscv64) plus the interactive
fabric.4th verification sessions used to test CART-PLOT and find the
VARIABLE alignment bug.
Migrates Hermes's message/channel lifecycle onto the Stadium's unified
heat/capacity economy: MSG-ALLOC/FREE-NODE and CH-ALLOC/FREE-NODE now
route entirely through stadium_admit()/stadium_evict(), replacing the
old local free-list + independent heat-field mechanism. Eight
kernel-only STADIUM-* FORTH primitives (ADMIT, EVICT, RES@, RES-PULL,
RES-PUSH, HEAT@, HEAT!, WORD-HEAT), VM.stadium_vm_id threaded through
all three vm_core.c dispatch sites (replacing item 4.1's hardcoded
vm_uuid_hera()), and the stadium_owner[idx] fix so evict-credit lands
in the VM that actually admitted a patron, not whoever owned cell 0.
This session's own contribution, on top of that pre-existing
implementation: found and fixed two bugs blocking the item's own K≡1.0
conservation self-check (HERMES-K was reading 0, not 65536):
- Q.SLOT admission-heat fix (capsules/hermes/init.4th): MSG-SEND/
CH-ACCEPT admitted with Q.1 (the entire fleet-wide "1.0" unit) per
item, a leftover from before the Stadium migration when each
message/channel had its own unconstrained heat field. Instantly
drained the shared, finite reservoir.
- Reservoir floor for word-execution admission (stadium_words.c):
stadium_word_dispatch() (item 4.1) pulls STADIUM_WORD_HEAT_QUANTUM on
every word dispatch, not just first admission -- exhausts a VM's
entire reservoir in ~32 dispatches, starving any application-level
economy sharing that VM's reservoir before it gets a chance to pull
anything. word_dispatch_pull() now clamps word-execution's own pulls
to leave a Q48_ONE/3 floor (same fair-share figure COMMON-CH's own
floor already uses); application-level pulls are unaffected.
- STADIUM-WORD-HEAT primitive + stadium_words_resident_heat(): the
floor deliberately leaves word-execution residents holding real
heat, invisible to HERMES-K's original formula (MSG+CH+reservoir,
no term for word patrons). Adding this term closes K to exactly
65536 on all three architectures.
Also rules on two open scope questions in FABRIC.md: MBR-ALLOC/
MBR-FREE-NODE stay off the Stadium (membership records have no heat
field, never did -- the acceptance bullet's inclusion of them was a
completeness gesture predating a check of the actual layout), and
records the effort number (12 implementation files, +759/-120 lines).
Verified: all three architectures boot clean, full self-test passes,
Stadium conservation closes exactly (resident_sum + reservoir =
Q48_ONE) at both the C/Stadium level and the FORTH-level HERMES-K
check.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The generic block subsystem (blk_format_or_load_disk) auto-reformatted
any disk lacking its own low-level 'STFR' header at attach time, before
Artemis's Forth-level BLANK/LithosAnanke/Unrecognized classification
ever ran -- so ART-HALT-UNRECOG's "Disk preserved" message was false.
Split detection from commit: an unrecognized/blank disk is now left
PROVISIONAL (geometry computed in memory only, all writes refused)
until explicitly confirmed via the new blk_subsys_confirm_format() /
BLK-CONFIRM-FORMAT primitive. Artemis calls it from ART-FORMAT and
ART-RESUME, never from ART-HALT-UNRECOG.
Verified on amd64/aarch64/riscv64: parity intact (identical dict_hash),
normal recognized-disk resume + persist-read unaffected, and a
regenerated disk/artemis-unrecognized-test.img (the old copy had itself
been silently corrupted by this exact bug) now stays byte-for-byte
identical across a halted boot on amd64 and riscv64.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
ABORT is documented and tested in this codebase as standard FORTH-79
behavior -- system_words_test.c:63: "Should clear stacks and return to
QUIT" -- meaning it should unwind all the way back to the outermost
interpreter loop, abandoning whatever's left of the current line/block.
The implementation only unwound one level: every place that checked
vm->abort_requested cleared it the instant it saw it, so it never
survived to propagate past the first nested frame.
This surfaced via Artemis's ART-HALT-UNRECOG (capsules/artemis/init.4th):
on an unrecognized disk it correctly printed "ARTEMIS HALT: unrecognized
disk content" and called ABORT, but WELCOME (the next line in the same
block) ran anyway, and Artemis announced ready to Hermes and joined the
fleet normally -- contradicting .claude/ARTEMIS.md's "Refuse to mount...
do not overwrite it" requirement. Root cause is general, not
Artemis-specific, and present identically in both the hosted and kernel
VM cores.
Fixed at every level execution can nest through, verified by exhaustively
grepping every !vm->error-gated continuation loop and adding the parallel
!vm->abort_requested check:
- execute_colon_word (src/vm.c, src/starkernel/vm/vm_core.c): stop
clearing the flag on return -- every colon-word call is a recursive
call to this same function, so leaving it set lets every enclosing
frame's own check also unwind.
- vm_interpret (src/vm.c, src/starkernel/vm/vm_core.c): stop parsing
further words in the current input string once the flag is set.
- exec_block_with_retry (src/starkernel/capsule/capsule_loader.c):
capsule birth's line-by-line block executor -- stop processing further
lines in the current block, but return 0 (not -1), so
capsule_exec_payload still loads later blocks in the same capsule
payload. Returning -1 here would have silently broken word definitions
in blocks that come after the aborting one for reasons unrelated to
why it aborted (concretely, Artemis's ART-PING/LOAD-DOE in blocks
4851/4852, which follow the entry block 4133).
- THRU and --> (src/word_source/block_words.c): stop processing further
blocks/lines in their own loops.
- DODOES (src/word_source/defining_words.c): the CREATE...DOES> runtime
has its own hand-rolled execution loop, separate from
execute_colon_word -- same bug class, same fix. Also guarded the
post-loop "if (vm->rsp < base_rsp) vm->rsp = base_rsp" clamp so it
doesn't fire on an abort exit -- ABORT's own reset_vm_state() already
set rsp; restoring it to base_rsp would have partially undone that.
- Both REPL loops (src/repl.c, src/starkernel/repl.c x2 call sites):
clear the flag after each line, mirroring the existing vm->error
pattern, so a mid-line abort doesn't silently freeze subsequent
interactive input.
Verified directly: ": AB-TEST 1 2 3 ABORT 999 . ; AB-TEST 42 . CR
777 . CR" -- 999 never prints (stops mid-colon-word), 42 never prints
(stops the rest of the same line), 777 prints fine (next line
unaffected). Artemis: WELCOME/"Artemis ready" no longer fires after the
halt message. No regression: all three architectures still show PASS:
persist-read, PASS: E2E msg flow, and matching dict_hash on the normal
(non-aborted) boot path; hosted test suite 965 passed / 0 failed.
Known follow-up, not fixed here (see memory for details): Artemis still
announces ready to Hermes via a separate call path (CD-INIT, block 4141)
that never went through capsule_exec_payload's block chain in the first
place, and the disk file still picks up incidental writes even on a
correctly-halted boot -- likely generic block-subsystem housekeeping,
not traced yet.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Chases down the amd64/aarch64/riscv64 "FAIL: persist-read" that
capsules/artemis/init.4th's ART-READ-TEST self-test has been reporting
in every boot log in this repo's history.
Root cause: not a code bug. disk/artemis.img had been stuck in a
corrupted state (valid LithosAnanke magic header, but data not matching
what ART-READ-TEST expects) since before this repo's own git history
begins -- already broken at the initial commit, carried over from the
pre-split monorepo. The FAIL was accurate: it correctly reported bad
data, not bad code.
Verified via a fresh disk/artemis-debug-roundtrip.img: format ->
self-test PASS -> write-test PASS -> reboot -> resume -> PASS:
persist-read, confirmed 3 times in a row. The write/read/persist code,
free map, block allocator, and C-level block subsystem cache/writeback
logic are all correct.
Fix: blanked disk/artemis.img and let a normal boot format + write-test
it fresh, then verified PASS: persist-read on amd64, aarch64, AND
riscv64 against the same reformatted image -- confirming the arch-neutral
on-disk format works cross-arch too (a boot on one architecture writes
data the other two can correctly read back).
disk/artemis-debug-roundtrip.img is kept as a regression fixture, already
in a known-good passing state -- a future break here is a real
regression, not fixture rot like artemis.img turned out to be.
disk/README.md: documented both images' state, and corrected a stale
claim that these images are managed via scripts/rundisk.sh -- that
script actually targets a separate, currently-unused disks/ (plural)
directory for the hosted VM's --disk-img= flag, not this kernel-QEMU
disk/ (singular) one.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>