Add VM-HEAT primitive: groundwork for a compudynamic turn-attractor (FABRIC-3.md §XIX)
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run

Prompted by reading the std79-doe K report: checked whether the campaign's
"real cross-VM dispatch load" was actually concurrent or strictly
serialized. It's serialized at two levels -- VM-EXEC's vm_interpret(target,
...) is a direct synchronous C call (Hera fully blocked until it returns),
and even the background physics tick (vm_tick(), vm_runtime.c) is driven
by each VM's own execution loop, so idle identities accrue zero ticks
between their own turns. K's perfect conservation (FABRIC-3.md §XVIII)
verifies sequential per-VM accounting correctness, not concurrent-access
safety, since there was never concurrent access to test.

Agreed direction: fix this without a scheduler, by reusing the same
least-dense-candidate judgment stadium_admit() already trusts for
eviction, applied to "whose turn is next" instead of "who gets evicted" --
a fleet-level turn-attractor giving the next turn to whichever live
identity currently has the lowest execution_heat_q48, no fixed round-robin,
no priorities, no preemption. Lives beside Stadium in
capsule_vm_physics.c (already the fleet-level consumer of Stadium
primitives, e.g. the K mechanism itself), not inside stadium.c ("the
floor" -- residency/eviction, a different concern from turn order) and
not a new subsystem.

This pass lands only the primitive the mechanism needs: VM-HEAT
( c-addr u -- heat-q48 ), pushing a named VM's current
execution_heat_q48 via vm_physics_heat_of() -- previously C-internal
only (doe_log_heat_by_name(), doe_log.c), never exposed to FORTH. Silent
0 on an unknown/dead name (no print/error), since a turn-attractor
scanning many candidates every turn shouldn't have to filter console
noise for names that simply aren't live. Registered everywhere
VM-EXEC/VM-CALL already are. Builds clean on all three architectures;
live-tested on amd64: Hera -> 65452, Hermes -> 43, unknown name -> 0, no
faults.

The turn-attractor loop itself (std79-doe.fth's trial ordering) is not
yet built -- next step, not done here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EXieurDfDSsDFdnSyusuWo
This commit is contained in:
Robert Allan James
2026-09-12 14:53:06 -04:00
co-authored by Claude Sonnet 5
parent 238ca95b3c
commit 5a9425b91b
7 changed files with 9102 additions and 260 deletions
+62
View File
@@ -2692,3 +2692,65 @@ wire (same interleaving effect as §XVII's write-up, just landing on a different
unaffected either way, since the K result does not depend on trial attribution being correct at
all.
## XIX. VM-HEAT primitive: groundwork for a compudynamic turn-attractor (2026-09-12)
**The real null hypothesis behind §XVIII's K result.** Reading the report prompted the right
question: was execution actually concurrent across the 9 live identity VMs, or strictly
serialized? Checked against the code, not assumed: `VM-EXEC` (`mama_word_vm_exec`,
`mama_forth_words.c`) calls `vm_interpret(target, cmd_buf)` directly on Hera's own call stack --
a synchronous C call, not a thread or coroutine switch. Hera is fully blocked until the target
VM's interpretation returns; exactly one VM's FORTH code executes at any instant. Worse, even
the *background* physics tick is not uniform across the fleet: `vm_tick()` (`vm_runtime.c`) is
called from *each VM's own* execution loop, driven by *that VM's own* word dispatch -- its own
doc comment already documents this as a previously-fixed bug (fleet physics used to gate on
Hera's tick count specifically and "almost never fired, since Hera-as-orchestrator mostly blocks
on VM-EXEC/VM-CALL dispatch that accrues to the target VM's tick count, not hers"). Net effect:
while VM A holds the turn, the other 8 identities accrue *zero* ticks and call `vm_tick()` *zero*
times -- completely dormant between their own turns, not just "not currently executing a word."
**What this means for §XVIII's result.** K's perfect conservation verifies that each individual
admit/evict/reservoir-transfer event was internally correct across many sequential events on 9
different VMs. It is **not** evidence about concurrent-access safety, because there was never any
concurrent access for it to be evidence of -- at any instant at most one VM's heat is changing,
everything else is static, so the fleet sum staying constant is a real check on the *sequential*
accounting, not a stronger claim about contention. Worth being honest about rather than letting
the report's "real cross-VM dispatch load" phrasing be read as implying more than it tested.
**Design direction agreed with Captain Bob: fix the gap without a scheduler.** A fixed
round-robin across identities would just be a different hardcoded policy -- not in the spirit of
a runtime whose whole design principle is finding its own attractors rather than being told what
to do. The chosen direction instead reuses a judgment this codebase already trusts elsewhere:
`stadium_admit()` picks the least-dense (coolest) resident as its eviction candidate when a
quota is full. Turn allocation is the same kind of decision -- "whose turn is next" instead of
"who gets evicted" -- so the plan is a fleet-level turn-attractor that gives the next turn to
whichever live identity VM currently has the lowest `execution_heat_q48`, not a fixed sequence
position. A VM idle longer becomes relatively cooler and naturally rises to the front; a VM
mid-burst stays hot and cedes turns. No priority levels, no time slices, no preemption -- the
same density comparison Stadium already uses, applied to "who goes next."
**Scoping: not the floor, not a parking lot, not a new subsystem.** `stadium.c` is "the floor"
in this project's own vocabulary (patrons "leave the floor" when reaped) -- its job is residency
and eviction, not turn order, so this doesn't belong inside it. `capsule_vm_physics.c` already
is the fleet-level layer that sits next to Stadium and consumes its primitives without living
inside `stadium.c` itself (it's exactly how the K mechanism, §XVIII, is built) -- a turn-attractor
reading per-VM heat to pick the coolest candidate is precisely the kind of fleet-wide decision
this file already exists to make. No new subsystem needed.
**Groundwork landed this pass: `VM-HEAT ( c-addr u -- heat-q48 )`** (`mama_forth_words.c`,
registered in all three places `VM-EXEC`/`VM-CALL` already are -- Hera's root vocabulary, her
MAMA vocabulary, and `register_child_vm_words()`). Pushes the named VM's current
`execution_heat_q48` via `vm_physics_heat_of()` -- previously C-internal only
(`doe_log_heat_by_name()`, `doe_log.c`), never exposed to FORTH. Silent 0 on an unknown/dead
name (no print, no error) -- a turn-attractor scanning many candidate names every turn shouldn't
have to filter console noise for names that simply aren't live. Builds clean, zero warnings, on
all three architectures; live-tested on amd64 (single boot, zuse only): `S" Hera" VM-HEAT .` -->
`65452`, `S" Hermes" VM-HEAT .` --> `43`, `S" NoSuchVM" VM-HEAT .` --> `0`, all as expected, no
faults.
**Not yet built: the turn-attractor loop itself.** This pass added only the primitive
(`VM-HEAT`) the mechanism needs, not the mechanism -- `std79-doe.fth`'s trial loop still runs
each of the 27 shuffled (identity, replicate) cells to completion in shuffle order, unchanged
from §XV. The actual heat-driven "coolest live identity goes next" turn loop, and re-verifying
the whole campaign (including whether K still holds under genuinely interleaved multi-VM
contention, which is the actual point of building this) is the next step, not done here.