Supplies the concrete definition §4 was missing. §4's claim that ranking is
read rather than decided is empty until the thing being read is a number.
Three quantities, not one:
Heat conserved share moved by traffic, sum = 1.0 always (already built)
Mass cells a patron occupies -- its footprint (new)
Density heat / mass -- heat per cell (new, derived)
K is left untouched. vm_physics_conserved() already defines it as a normalised
heat share summing to Q48_ONE, not an occupancy ratio; defining it as
mass/capacity would have contradicted implemented, tested code.
Ranking, admission-when-full, and migration hysteresis all read off density
with no policy and no damping constant.
Two corrections to §4:
- The self-limiting claim keeps its conclusion but loses its mechanism. A hot
entry is easier to reach, not harder; the real governor is conservation,
since heat is zero-sum and capped at 1.0.
- "Density generates heat" reverses the causality. Traffic confers heat;
density is heat per cell, derived downstream.
Open: mass depends on payload threshold and header size (§12 Q1, Q2), which are
now prerequisites rather than sizing details; and vm_physics_touch scales heat
transfer by wall-clock time, which §18.5 forbids and which must be restated on
tick count before L0 can use it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Captures the 2026-08-03 design session: collapsing the four independent
heat/TTL/pin implementations (blocks, messages, console cells, ACLs) into
one bounded Stadium of fixed-size entries, driven by an engine below every VM.
Sections 1-15 are the original design argument. Sections 16-18 add:
- 16 Substrate findings. No IRQ return path exists on aarch64 or riscv64;
riscv64's time base is a hardcoded 1 GHz guess; the dictionary already
carries six of the seven entry wires; the engine must stay deterministic.
- 17 Patrons. TTL, heat decay and pin are three distinct mechanisms on one
tick, not a type field. Reap means leaves the floor, not destroyed. The
framebuffer is a utility, not a patron. Dynamic in capacity, static in
structure.
- 18 The engine (L0). L0 and L8 bookend the gated loops L1-L7, both ungated.
Jacquard stays 7-bit/128 states, accounting for L0 by its absence.
Dispatch enumerates behaviours, never patron kinds.
Determinism traced end to end and confirmed intact: TIME-TRUST is measured
and never fed back, inference inputs are wholly execution-derived, decay is
tick-based, and the parity hash covers only word name and execution_heat.
One pre-existing exception recorded — vm_physics_touch scales fleet heat by
wall-clock elapsed time, outside the parity path.
Draft. Sections marked DECIDED / LEANING / OPEN throughout.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Serial logs for the 30-replicate ART-STRESS-CAMPAIGN run, one per
architecture:
logs/20260802-160052/amd64 30 reps x 50 trials, 1500/1500 pass
logs/20260802-173226/aarch64 30 reps x 50 trials, 1500/1500 pass
logs/20260802-181803/riscv64 30 reps x 50 trials, 1500/1500 pass
4500 trials, zero failures. These are the audit trail for the claim that
the block_words.c stale-pointer cache-aliasing fix holds at scale, and for
cross-arch disk read/write/persistence.
Committed as raw blobs, matching the ~6.7GB of existing log objects in
history. Note that .gitattributes already declares logs/**/*.log and the
bare-metal CSVs as LFS-tracked, but git-lfs is not installed in this
environment, so those filters are silent no-ops -- the 744MB log at
logs/20260802-115945/ is likewise a raw blob, not a pointer. Flagged for a
proper fix (install git-lfs, confirm server-side LFS support, decide
whether to migrate existing history) rather than half-applying LFS to only
these three files.
Co-Authored-By: Claude Opus 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>
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>
The flat-pool storage scope ("Immediate Goal") has been implemented for
some time -- capsules/artemis/init.4th boots live in the Tripod fleet on
every kernel boot -- but the doc still said "do not begin implementation
without explicit instruction from Captain Bob" in two places, and
CLAUDE.md's pointer still said "build-authorization deferred."
.claude/ARTEMIS.md: replaced both stale lines with a dated Build Status
section giving an honest per-criterion accounting rather than a blanket
"done": 4 of 6 acceptance criteria fully confirmed (boot-state detection,
free-map init, fetch/persist, and -- as of the persist-read fix earlier
today -- cross-boot/cross-arch persistence), K-conservation implemented
but not hard-asserted, and the unrecognized-disk halt implemented but
never actually exercised against real unrecognized content. Future
material (zones, USB hot-plug, ACL records, PKI) remains correctly
marked deferred -- unchanged.
.claude/CLAUDE.md: updated the Artemis pointer line so it's consistent
with the above instead of contradicting it.
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>
Ran the amd64 kernel acceptance leg 5 times back to back as the punch
list's action item asked. dict_hash was byte-identical across every run
for every VM (Artemis, both Hermes instances, Hera/MAMA_INIT), and
matched aarch64/riscv64 exactly each time. Not jitter, and not
stable-but-different either.
The underlying mechanism the hypothesis pointed at is still real and
unchanged (capsule_dict_hash_hook() still folds execution_heat into the
hash; amd64 still runs its timer in RELATIVE mode under this
hypervisor) — but PARITY:MAMA_INIT and the child-VM PARITY:BIRTH lines
all print before the heartbeat starts, and heat only decays on
heartbeat ticks, so there's no window for the timer's non-determinism to
reach execution_heat before any of these hashes get computed. Most
likely the original 2026-07-24 observation was a one-off (loaded host
machine, coincidental timing), not a real gap.
No code change. No amendment to CLAUDE.md's acceptance criteria needed —
"identical dict_hash across all three architectures" holds up under
repeated testing. Closure note added to the punch list; the 5 verification
runs' logs and DoE CSVs are kept as the supporting evidence.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Resolves item #3 of docs/working/archive/session-logs/2026-07-24-punch-list.md
("riscv64 hosted build isn't reachable via plain make"), the last open
item from that list. Decided against options (b) chasing GCC's riscv64
nanosleep-visibility failure at its root (undiagnosed, open-ended) and
(c) leaving it manual — instead wired the already-verified clang recipe
(commit 4485c38 / e287334) into the Makefile, mirroring the existing
rpi4-cross pattern.
Makefile: new riscv64-clang target. CFLAGS deliberately does not reuse
$(BASE_CFLAGS) (hardcodes -std=c99); clang needs -std=c11 -pthread here
instead. Registered in `make help` and .PHONY.
docs/lithosananke/hosted-acceptance-test/README.md: riscv64 section now
points at `make riscv64-clang` instead of the long manual invocation.
Updated Background section and commit list to reflect that all three
punch-list items touching this doc (#1 asm fix, #2 doc command, #3 make
target) are now resolved.
Verified: `make riscv64-clang` produces a binary with identical results
to the manual command it replaces (965 passed / 0 failed, "ALL
IMPLEMENTED TESTS PASSED!", "3 Goodbye!" for the piped acceptance script).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Completes the highest-priority item from
docs/working/archive/session-logs/2026-07-24-punch-list.md (item #1),
intentionally deferred out of commit 4485c38 as a separate, more careful
change.
vm_pop_asm/vm_rpop_asm's inline asm referenced the dsp/rsp memory operand
twice (read near the top, write-back near the bottom) while also writing
a plain register output operand (%[val]) in between. Nothing pinned the
address register computed for the memory operand across that gap, so
clang's allocator could reuse it for %[val], corrupting the write-back.
GCC happened to pick different registers and never hit it — this repo's
kernel build uses GCC and USE_ASM_OPT is never defined there, so the bug
was latent, not live, prior to this fix.
Fixed by reordering: write dsp/rsp back before loading the popped value,
so the memory operand's final use has already happened by the time the
output register is live. Same fix as the old pre-split monorepo's master
commit 4db9946a, re-derived here since that commit lives in a different
repository post-split.
Verified empirically, not just theoretically: rebuilding the riscv64
hosted binary with the documented clang -O3 -DUSE_ASM_OPT=1 acceptance
recipe went from 955 passed / 10 failed (all CASE.* control-flow tests —
exactly what stack-pop corruption would hit) to 965 passed / 0 failed,
"ALL IMPLEMENTED TESTS PASSED!", with nothing else changed. All three
Makefile.starkernel kernel builds still compile clean; the change is
inert there since USE_ASM_OPT is never defined for the kernel build.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Addresses items #1 (partial) and #2 of
docs/working/archive/session-logs/2026-07-24-punch-list.md.
docs/lithosananke/hosted-acceptance-test/README.md:
- riscv64 leg used riscv64-linux-gnu-gcc, which fails to build this tree
(nanosleep visibility under -std=c99). Replaced with the working
clang-18 --target=riscv64-linux-gnu --sysroot=/usr/riscv64-linux-gnu
invocation, verified end-to-end.
- All three arch sections referenced a -c "<script>" flag that has never
existed in cli.c/main.c. Corrected to the working
`echo "..." | starforth -s` pattern, verified on all three architectures.
- Updated Prerequisites: qemu-user alone is sufficient (guest binaries are
static; qemu-user-static provides static *emulators*, not required here).
Source fixes (ported from the old pre-split monorepo's master, commit
4db9946a, where they were made but never carried over to this line):
- src/math_portable.c: `-100LL << 16` is UB (shifting a negative value)
under clang's -Wshift-negative-value; changed to `-(100LL << 16)`.
- src/physics_pipelining_metrics.c: removed dead q48_mul_q48()
(-Wunused-function under clang; GCC doesn't flag this by default).
- src/word_source/editor_words.c: removed dead set_scr() (same reason).
These three were required just to get the documented clang build to
compile at all. The punch list's higher-severity item — a genuine
SIGSEGV-causing register-reuse hazard in vm_pop_asm/vm_rpop_asm
(include/vm_asm_opt_riscv64.h) — is intentionally NOT included here; it's
a separate, more careful change and isn't required for this build to
succeed (latent only under clang; this repo's kernel build uses GCC).
Verified: all three hosted builds compile and run correctly (amd64
native, aarch64 via qemu-aarch64, riscv64 via qemu-riscv64), each
printing "3 Goodbye!" for the piped `1 2 + . BYE` script. All three
Makefile.starkernel builds (amd64/aarch64/riscv64) still compile cleanly
with these shared vendored-source changes.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Write-up of the 2026-08-02 riscv64 boot crash investigation and fix
(commit 7366275), in the same style as docs/lithosananke/amd64-isr-fix.
Co-Authored-By: Claude Sonnet 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>
LithosAnanke is now its own repository rather than a branch inside the
combined StarForth/LithosAnanke monorepo, so this drops the old
master(StarForth)/lithosananke(kernel) branch-topology framing in favor of
the current reality: this repo's master is the sole LithosAnanke production
line. Ground-truths numbers that had drifted (kernel tree file count,
word_source file count, capsule count), reframes the vendored VM source as
the embedded engine it actually is rather than a second production target,
and independently verifies the ACL kernel-parity and DoE-campaign claims
against the actual current code. Fixes README's self-referential
"see master branch" link to point at the separate StarForth repo instead.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>