stadium: wire STADIUM_CAPACITY_TICK in as a flat threshold, not a scheduler

Closes FABRIC-2.md's last open §12 Q5 question. fleet_heartbeat_tick_count
is fed by every live VM's own vm_tick(), not one VM's, so it was reaching
HEARTBEAT_INFERENCE_FREQUENCY (shared/borrowed from the per-VM inference
gate) several times faster than intended with more than one VM live -
backwards from FABRIC.md §22.4's required ~1000:1 separation.

What's actually gated turned out to be low-stakes: vm_physics_tick()
(capsule_vm_physics.c:397) is a passive statistics refit - re-sorts a
window of past heat-transfer samples and recomputes a median rate
estimate. It doesn't move heat or arbitrate capacity. Firing too often
just meant a noisier statistic recomputed more frequently than planned,
not incorrect behavior.

Considered and explicitly rejected: scaling the threshold by live VM
count at the check site. That's the first brick of a scheduler - reading
fleet state to adjust a rate dynamically - which this project has
deliberately avoided building. Implemented instead: STADIUM_CAPACITY_TICK
(existing Kconfig symbol, defined but never read by any code path) now
gates vm_physics_heartbeat_tick()'s call directly, replacing the borrowed
HEARTBEAT_INFERENCE_FREQUENCY. Default bumped 1000 -> 4000, a flat
constant picked once for Tripod's known 4-VM topology, same kind of
placeholder as every other frequency knob in Kconfig.kernel - not
computed from anything at runtime. Renamed fleet_last_inference_tick ->
fleet_last_capacity_tick to match. Still one clock, one counter
(fleet_heartbeat_tick_count) - just a bigger flat divisor on it.

Three-arch QEMU acceptance: all clean to ok>, identical Stadium
conservation invariant on all three (resident_sum=43691 reservoir=21845
sum=65536). logs/20260815-093425/amd64, logs/20260815-093521/aarch64,
logs/20260815-093641/riscv64.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-08-15 09:37:58 -04:00
co-authored by Claude Sonnet 5
parent c5442c8377
commit 89d8c08582
10 changed files with 25755 additions and 46 deletions
+30 -26
View File
@@ -763,34 +763,38 @@ all four `.claude/*.md` files, and correcting `.claude/CLAUDE.md`'s own pointer
running both *after* Artemis lands, since Artemis's own storage/timing work is likely to
perturb whatever gets measured now — a recommendation, not a ruling made here.
**Needs Captain Bob's ruling:**
- [x] **§12 Q5's `STADIUM_CAPACITY_TICK` wiring — CLOSED 2026-08-15.** A real, verified
ordering violation, live today: with Hermes restored (item 4.2) and `STADIUM_MAX_VM_COUNT`
(now RAM-derived, Section G) at more than one VM, the shared fleet counter reached
`HEARTBEAT_INFERENCE_FREQUENCY` several times faster than §22.4's required 1000:1
separation intended.
- [ ] **§12 Q5's `STADIUM_CAPACITY_TICK` wiring.** A real, verified ordering violation, live
today, not latent: with Hermes restored (item 4.2) and `STADIUM_MAX_VM_COUNT` defaulting to
4, the shared fleet-capacity counter can already reach `HEARTBEAT_INFERENCE_FREQUENCY` up to
~4× sooner in wall-clock terms than a single VM's own heat-inference gate — backwards from
§22.4's required 1000:1 separation, confirmed by this document's own §12 Q5 investigation
above.
**What was actually at stake, checked before fixing anything:** the thing gated by this
threshold, `vm_physics_tick()` (`capsule_vm_physics.c:397`), is a passive statistics
refit — it re-sorts a sliding window of past heat-transfer samples and recomputes a median
rate estimate. It does not move heat, does not arbitrate capacity, does not decide
anything. Firing it too often just meant a noisier statistic recomputed more frequently
than planned — not incorrect behavior, not a live bug in the urgent sense.
**Correction 2026-08-15 — the fix must stay inside the one-clock rule (`FABRIC.md` §16.4
GAP-A1, §17.1 "two measures, one clock... this does not mean two clocks").** `fleet_
heartbeat_tick_count` (`capsule_vm_physics.c:157`) is a single counter, incremented once
per call to `vm_physics_heartbeat_tick()`, which itself only ever fires from inside the
execution-paced `vm_tick()` (`vm_runtime.c:141`) — never from a hardware timer. So there is
only one clock here, exactly as ruled; the earlier framing of this item ("give the capacity
tick its own named constant... independent of per-VM tick counts") was worded ambiguously
enough to read as "add a second clock," which is not the fix and must not be built as one.
The actual defect is narrower: `vm_tick()` calls `vm_physics_heartbeat_tick()` from *every*
live VM's own dispatch (deliberate, `vm_runtime.c:126-138`'s own comment explains why — the
fleet-capacity signal needs to reflect aggregate activity, not just Hera's), so `fleet_
heartbeat_tick_count` advances on the fleet's *combined* stream while `HEARTBEAT_INFERENCE_
FREQUENCY` was written assuming a single VM's stream. Same one counter, same one clock —
the threshold compared against it just doesn't account for multiple VMs feeding it at once.
**Question for Bob:** the fix is to give `STADIUM_CAPACITY_TICK` a larger *threshold value*
checked against the existing `fleet_heartbeat_tick_count` (still one clock, one counter —
just a bigger divisor, e.g. scaled by the live VM count so it restores something closer to
the required 1000:1 separation), not a second tick source. Wire it in that way, or resolve
the ordering some other way?
**Fix, explicitly NOT the shape first proposed.** The initial framing ("threshold scaled
by live VM count") was rejected by Captain Bob before implementation — correctly: reading
VM count at the check site to adjust a rate dynamically is the first brick of a scheduler,
which is exactly the kind of machinery this project has deliberately avoided building.
Implemented instead: `STADIUM_CAPACITY_TICK` (existing Kconfig symbol, previously defined
but never read by any code path) now gates `vm_physics_heartbeat_tick()`'s call to
`vm_physics_tick()` directly, replacing the borrowed `HEARTBEAT_INFERENCE_FREQUENCY`.
Default bumped from 1000 to a flat **4000** — a fixed constant picked once for Tripod's
known 4-VM topology, same kind of placeholder as every other frequency knob in
`Kconfig.kernel`, not computed from anything at runtime. Renamed
`fleet_last_inference_tick``fleet_last_capacity_tick` to match. Still one clock, one
counter (`fleet_heartbeat_tick_count`), unchanged — just a bigger flat divisor on it.
Files: `Kconfig.kernel`, `include/starforth_config.h`,
`include/starkernel/vm/stadium.h` (comment only),
`src/starkernel/capsule/capsule_vm_physics.c`. Three-arch QEMU acceptance, all clean to
`ok>`, identical Stadium conservation invariant on all three
(`resident_sum=43691 reservoir=21845 sum=65536`): `logs/20260815-093425/amd64/`,
`logs/20260815-093521/aarch64/`, `logs/20260815-093641/riscv64/`.
**Closed-by-ruling, not reopened this pass** (each already has a recorded "flag and leave"
decision from Captain Bob — reversing that without being asked would be exactly the