Punch list §25 item 4.3.5 complete.
New ioapic.c/i8042.c drivers (MADT-derived I/O APIC base, no hardcoded
constants) plus a KBD-SCAN/KBD-DEBUG diagnostic word pair. Three real
bugs found and fixed en route, all blocking this item's own acceptance:
a fatal LAPIC spurious-vector crash (nothing had driven a real external
interrupt through the I/O APIC before), OVMF leaving the keyboard device
itself scanning-disabled (0xF4 fix), and isr.S's stub table only having
individually-numbered stubs through vector 32 -- everything above that,
including our IRQ1 vector 33, silently reported as vector 255 regardless
of which IDT slot actually fired. Verified live via QEMU sendkey against
KBD-SCAN: correct XT Set-1 make/break codes for two different keys.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Reversing the earlier decision to track fb/ in git (made when first
setting up the directory for item 4.3.2). Captain Bob: these are
disposable screenshots for eyeballing framebuffer output during Console
work, never meant to be committed. Removed from git tracking (git rm
--cached) and added to .gitignore; files that still exist locally are
untouched, deletions already made locally are left as-is.
Also fixes item 4.3.4's amd64-only blind spot found in the process:
aarch64/riscv64 had no framebuffer device at all (GOP: protocol not
found) -- the "all three architectures boot clean" checks run all session
were REPL/dict_hash parity, a different thing from GOP presence, and
conflating the two was an error. Added -device ramfb (EDK2's
firmware-only GOP framebuffer) to both architectures' qemu targets in
Makefile.starkernel. Both now report GOP: linear framebuffer found at
800x600; cube rendering verified correct on both (screenshots not
committed, per the untrack above -- verified visually this session).
Standard three-arch acceptance boot re-run afterward, all clean,
dict_hash identical and unchanged from before this fix.
vm_find_word() and dict_find_word_heat_aware() reference the same extern
globals (sf_fc_list/sf_fc_count/sf_fc_cap) but GCC compiled cross-TU
references to them with GOT-indirect addressing (R_X86_64_REX_GOTPCRELX)
under -fPIC. This freestanding, statically-linked UEFI PE image has no
dynamic linker to populate a GOT, so those reads silently returned NULL
instead of the array's real address -- amd64-only, and exquisitely
sensitive to unrelated code-size changes since the choice between direct
and GOT-indirect addressing is a per-call-site GCC heuristic.
Fix: -fno-pic -fno-pie for amd64 only (ARCH_CFLAGS, overriding
COMMON_CFLAGS's -fPIC, which riscv64's -shared loader link still needs).
Also removes -DPLATFORM_TIME_NO_INLINE, a prior one-off workaround for
the identical bug applied to sf_monotonic_ns() specifically, now
redundant. Adds R_X86_64_PC32/R_X86_64_PLT32 handling to
elf_apply_relocations() as a robustness fix for the non-monolithic
split-build path (dead code for the current monolithic boot, where OVMF's
own PE loader relocates the image, not this loader).
Verified: all three architectures boot clean and pass the full item-4.2
Hermes self-test, including MSG-DELIVER-ALL, which previously triggered
the corruption on amd64 only. Write-up in FABRIC.md under item 4.2.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Punch list §25 item 4.1 complete.
Replaces the round-robin hotwords cache with Stadium density-ranked
admission/eviction on the kernel side, via the §17.7 reservoir mechanism and a
kernel-side word_id -> cell_index map (no DictEntry change, dict_hash
untouched). Adds stadium_birth_hera() to close the cell-0 panic hazard,
STADIUM_WORD_HEAT_QUANTUM/STADIUM_WORD_COOL_RATE_Q48 Kconfig knobs (flagged
untuned), and a stadium_word_forget() FORGET coherence hook to close a
recycled-word_id aliasing gap.
Verified: all five hotwords_cache_* call sites in dictionary_management.c
bypassed under __STARKERNEL__; word dispatch feeds the Stadium at all three
vm_core.c physics_execution_heat_increment() sites; hosted make unaffected;
all three architectures booted to ok> with matching dict_hash
(0x3d4e1daf289da94f) and matching conservation stats (promotions=354
evictions=0, resident_sum=65536 reservoir=0 sum=65536).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Punch list §25 item 3.2 complete.
stadium_boot_init() (src/starkernel/vm/stadium.c) sizes the global
cell array at boot from a real memory-budget query rather than a
hardcoded count: pmm_get_stats().free_bytes at the point of
allocation, times the new STADIUM_MEMORY_PERCENT Kconfig symbol
(default 1%), rounded down to whole 64-byte cells. Matches §17.6's
position (b) literally. Also allocates the header/continuation
discriminator bitmap item 3.1 declared but did not allocate. Both are
kmalloc'd and explicitly zero-filled (kmalloc does not zero).
Called from kernel_main.c immediately before sk_vm_bootstrap_parity(),
i.e. before any VM exists (§6). Failure is soft -- logs and continues,
does not halt boot -- matching the existing precedent one line below
it (VM bootstrap parity failure does the same).
Added a "Stadium: N cells (M KB)" boot console line at the allocation
site so the acceptance logs are evidence the array was actually
allocated, not just that the kernel still boots -- the same blind spot
item 3.1's uncompiled-header gap exposed.
Verified: three-architecture boot (amd64, aarch64, riscv64), all
reaching ok> with identical dict_hash=0x3d4e1daf289da94f matching the
item-3.1 baseline, and the Stadium boot line confirmed present in all
three serial logs (amd64: 74234 cells/4639 KB, aarch64: 161329
cells/10083 KB, riscv64: 76122 cells/4757 KB).
Not built here, reported per §25.0 rule 3: per-VM free lists (§22.3)
-- granted when Hera assigns quota, not this item's scope.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Punch list §25 item 3.1 re-closed after reopening.
Items 1.1 and 1.4's resolutions both explicitly named this item as
where their Kconfig symbols would be implemented, but 3.1's own stated
scope never mentioned them, so the first close missed both:
- STADIUM_CONTAINS_DEPTH_MAX (default 5) -- item 1.1's contains-chain
depth cap. No consumer yet; reap-gating enforcement is item 3.5.
- STADIUM_CAPACITY_TICK (default 1000) -- item 1.4's capacity
arbitration cadence in virtual ticks. No consumer yet; capacity
arbitration itself is not on the punch list.
Both added following STADIUM_MAX_VM_COUNT's exact pattern:
Kconfig.kernel entry, Makefile.starkernel kconfig_int +
VM_FEATURE_FLAG_VARS forwarding, starforth_config.h fallback default.
stadium.h now includes starforth_config.h and carries two more
C99-portable compile-time checks proving both symbols are defined and
sane, same discipline as the byte-count checks. Declaration only --
not inventing the consuming logic to close this out early.
Verified: three-architecture boot (amd64, aarch64, riscv64), all
reaching ok> with identical dict_hash=0x3d4e1daf289da94f, re-run after
the reopening.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Punch list §25 item 3.1 complete.
Defines StadiumPatronHeader and StadiumContinuationCell in new
include/starkernel/vm/stadium.h, unioned as StadiumCell per §3's
closed two-valued union. src/starkernel/vm/stadium.c added to
Makefile.starkernel's LOADER_EXTRA_SRCS/KERNEL_EXTRA_SRCS so the
header's compile-time size checks are actually compiled, not merely
included by something that never builds.
Discriminator ruled an external side bitmap (Captain Bob), not a
header field -- amended into §3 and §23.3 before this code was
written. Item 3.1 declares the bitmap's purpose/indexing in a comment
only; allocating it is item 3.2's scope.
Both cell shapes counted for real at exactly 64 bytes with zero
compiler-inserted padding (three C99-portable negative-array-size
assertions -- no _Static_assert, this project targets C99). Header
matches §23.3's original 32+32 split unchanged, since the
discriminator moving outside the cell left nothing to compete for that
space. Continuation cell matches item 1.12's 4+60 figure unchanged for
the same reason.
Verified the size assertion is actually live: broke it to 63,
confirmed the build failed with the expected negative-array-size
error, restored it, confirmed a clean compile.
Verified: three-architecture boot (amd64, aarch64, riscv64), all
reaching ok> with identical dict_hash=0x3d4e1daf289da94f matching the
item-2.2 baseline. Confirmed stadium.o present in both obj/loader/vm
and obj/kernel/vm post-build on amd64, closing the gap the item-2.2 WIP
exposed (an uncompiled header proves nothing).
Left open, not fabricated: §23.4 #2 ("does a typical message fit in
one cell") is unanswerable today -- no message patron struct exists
anywhere in this tree yet.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Partial work toward FABRIC.md punch list item 2.2. Adds the
STADIUM_MAX_VM_COUNT Kconfig symbol (default 4, per item 1.5) wired
through Makefile.starkernel, a new CAPSULE_RUN_ERR_FLEET_FULL result
code, and a vm_registry_live_count() helper in capsule_birth.c that
counts LIVE VMs only (distinct from the existing monotonic
vm_registry_count, which never decrements on death).
NOT YET DONE: nothing calls vm_registry_live_count() yet -- the actual
birth-refusal check is not wired into capsule_birth_baby(). Not built,
not boot-tested. FABRIC.md's item 2.2 checkbox is deliberately left
unchecked; this commit exists only to save in-progress work before a
pause, not to claim the item complete.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Introduces src/starkernel/heartbeat.c as the shared top/bottom-half
implementation of heartbeat_init/tick/service/ticks/trust/state, replacing
the per-architecture duplicates in amd64/riscv64/aarch64 timer.c. Each
arch's timer.c now contributes only heartbeat_read_counter() (rdtsc /
rdtime / CNTPCT_EL0). Per the GAP-A1 ruling the top half stays counter+
latch only; heartbeat_service() (called every REPL idle iteration,
unconditionally per FABRIC.md's fidelity note) does the window/variance/
trust work outside interrupt context. vm_tick()'s call sites are
unchanged -- the engine still runs on the virtual tick.
Per FABRIC.md §26 (ruled 2026-08-03): wires Loop #7's execution-derived
stable/volatile signal into the physical re-arm period. vm_runtime.c's
existing Loop #7 site now calls heartbeat_set_adaptive_period_ns() with
tick_target_ns ratio-rescaled onto a 10ms kernel base (not the hosted
10us HEARTBEAT_TICK_NS -- see §26.3 for the scale mismatch). Each
architecture's re-arm function (apic_timer_rearm() on amd64/aarch64,
riscv64_timer_rearm()) now converts heartbeat_next_period_ns() to its
own raw counter units instead of a fixed constant; amd64 gained a
rearm function it didn't previously need, since periodic-mode auto-reload
never required one before this item.
Verified: all three architectures build with no new warnings and boot
cleanly to ok> with dict_hash=0x3d4e1daf289da94f, unchanged from the
pre-change baseline -- no regression. Verified NOT achieved: live re-arm
period variation under load. A temporary diagnostic (added and reverted)
confirmed Loop #7 never actually fired during a live QEMU session -- a
synthetic word-execution loop drove ~6,500 executions, past the 1000-tick
inference frequency, without tripping vm_tick_inference_engine()'s
pre-existing !vm->rolling_window.is_warm gate. That gate predates this
item and was not investigated -- out of scope. FABRIC.md's Done-when is
amended to record this honestly rather than claim it.
Punch list §25 item 0.8 complete (per amended, weaker acceptance -- see
the item's own annotation).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The 30-replicate Artemis surface-stress campaign ran across all three
architectures: 30 reps x 50 trials x 3 arches = 4500 trials, zero failures.
The block_words.c stale-pointer cache-aliasing fix holds at scale.
Adds ART-STRESS-CAMPAIGN (block 4174) plus ART-STRESS-REP rep-tagging in
the CSV header/summary emitters, so a multi-replicate run is distinguishable
in the serial log. Campaign auto-invoke is left enabled in block 4170 for
now; Makefile.starkernel's QEMU boot deadlines stay at 12h to accommodate
long-running experiments.
Adds docs/working/architecture/ARTEMIS-COMPUDYNAMICS-IMPLEMENTATION-PLAN-20260802.md,
which documents the real gap this campaign exposed: block heat and message
heat do not decay at all. ART-TICK has zero call sites anywhere in the tree,
and HERMES-TICK has zero C call sites -- every caller is Hera poking it by
hand. BLK-HEAT@/MSG-HEAT@ read a number nothing ages, so blocks never reap
by cooling and message TTL never expires on its own.
The plan mirrors word-level physics as the reference model: lazy decay at
each access point against vm->heartbeat.tick_count, plus a bounded
background sweep with a resumable cursor (the existing answer to "sweeping
22,998 blocks per tick is too expensive"). Phase 1 Artemis, Phase 2 Hermes,
Phase 3 K participation deferred behind the Logical BAM.
The plan's preamble also records a wrong turn taken while investigating:
chasing VM-fleet heat instead of block heat, and building synthetic
Hera-driven VM-EXEC calls to force a physics reading -- which TRIPOD.md
prohibits outright. That work was reverted; the record is kept so it isn't
repeated.
Status: plan approved in shape, NOT final and NOT started. Six open
questions need answers and further design discussion is pending.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
kernel_main on riscv64 ran directly on EDK2's UEFI boot-time stack, with
no dedicated stack switch — amd64 has always had a kernel_entry.S
trampoline for exactly this reason (its own comment: "the FORTH
interpreter + DOE experiment loop can easily exceed that depth").
aarch64 happens to get away without one because its firmware's default
stack is apparently larger, but that was never a guarantee.
On riscv64 the VM bootstrap's call depth (27 word-registration modules
-> physics/SSM init -> Tripod capsule birth) overflowed that small
stack, corrupting a return address and producing a wild jump / page
fault right after vm_init_with_host() returned — reproduced consistently
across the 2026-08-01 DoE campaign logs.
- src/starkernel/arch/riscv64/kernel_entry.S (new): RISC-V stack-switch
trampoline mirroring amd64's, giving the kernel a dedicated 2 MiB BSS
stack before anything deep runs.
- kernel_main.c: riscv64 now builds kernel_main_impl (invoked via the
trampoline) instead of kernel_main directly, same pattern as amd64.
- Makefile.starkernel: wires the new file into the riscv64 build.
- uefi_loader.c: RAW_LOG() was silently a no-op on every non-amd64 arch;
added a real raw-UART writer for riscv64 (QEMU virt's uart8250 at MMIO
0x10000000) so existing loader diagnostics actually produce output.
Verified: all three architectures boot clean to [Hera] ok> in the
required order (amd64, aarch64, riscv64); logs and DoE CSVs from these
runs included.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The aarch64 loader link step hardcoded the unversioned "lld-link", which
isn't on PATH by default on this Debian/Ubuntu setup (the package only
installs lld-link-18 under /usr/bin; unversioned lld-link lives under
/usr/lib/llvm-18/bin). CI worked around this with an explicit PATH prefix
in the workflow; a local build without that PATH override failed. Now
auto-detects whichever name resolves, falling back to the versioned name.
Verified: aarch64 builds clean with the default PATH.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Kernel version only — the embedded StarForth engine version (3.1.0) is left
alone since it tracks a vendored copy that has genuinely diverged from the
standalone StarForth repo, not something to auto-sync. Verified builds on
amd64, aarch64, and riscv64.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>