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:
co-authored by
Claude Sonnet 5
parent
c5442c8377
commit
89d8c08582
+30
-26
@@ -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
|
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.
|
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
|
**What was actually at stake, checked before fixing anything:** the thing gated by this
|
||||||
today, not latent: with Hermes restored (item 4.2) and `STADIUM_MAX_VM_COUNT` defaulting to
|
threshold, `vm_physics_tick()` (`capsule_vm_physics.c:397`), is a passive statistics
|
||||||
4, the shared fleet-capacity counter can already reach `HEARTBEAT_INFERENCE_FREQUENCY` up to
|
refit — it re-sorts a sliding window of past heat-transfer samples and recomputes a median
|
||||||
~4× sooner in wall-clock terms than a single VM's own heat-inference gate — backwards from
|
rate estimate. It does not move heat, does not arbitrate capacity, does not decide
|
||||||
§22.4's required 1000:1 separation, confirmed by this document's own §12 Q5 investigation
|
anything. Firing it too often just meant a noisier statistic recomputed more frequently
|
||||||
above.
|
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
|
**Fix, explicitly NOT the shape first proposed.** The initial framing ("threshold scaled
|
||||||
GAP-A1, §17.1 "two measures, one clock... this does not mean two clocks").** `fleet_
|
by live VM count") was rejected by Captain Bob before implementation — correctly: reading
|
||||||
heartbeat_tick_count` (`capsule_vm_physics.c:157`) is a single counter, incremented once
|
VM count at the check site to adjust a rate dynamically is the first brick of a scheduler,
|
||||||
per call to `vm_physics_heartbeat_tick()`, which itself only ever fires from inside the
|
which is exactly the kind of machinery this project has deliberately avoided building.
|
||||||
execution-paced `vm_tick()` (`vm_runtime.c:141`) — never from a hardware timer. So there is
|
Implemented instead: `STADIUM_CAPACITY_TICK` (existing Kconfig symbol, previously defined
|
||||||
only one clock here, exactly as ruled; the earlier framing of this item ("give the capacity
|
but never read by any code path) now gates `vm_physics_heartbeat_tick()`'s call to
|
||||||
tick its own named constant... independent of per-VM tick counts") was worded ambiguously
|
`vm_physics_tick()` directly, replacing the borrowed `HEARTBEAT_INFERENCE_FREQUENCY`.
|
||||||
enough to read as "add a second clock," which is not the fix and must not be built as one.
|
Default bumped from 1000 to a flat **4000** — a fixed constant picked once for Tripod's
|
||||||
The actual defect is narrower: `vm_tick()` calls `vm_physics_heartbeat_tick()` from *every*
|
known 4-VM topology, same kind of placeholder as every other frequency knob in
|
||||||
live VM's own dispatch (deliberate, `vm_runtime.c:126-138`'s own comment explains why — the
|
`Kconfig.kernel`, not computed from anything at runtime. Renamed
|
||||||
fleet-capacity signal needs to reflect aggregate activity, not just Hera's), so `fleet_
|
`fleet_last_inference_tick` → `fleet_last_capacity_tick` to match. Still one clock, one
|
||||||
heartbeat_tick_count` advances on the fleet's *combined* stream while `HEARTBEAT_INFERENCE_
|
counter (`fleet_heartbeat_tick_count`), unchanged — just a bigger flat divisor on it.
|
||||||
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.
|
Files: `Kconfig.kernel`, `include/starforth_config.h`,
|
||||||
**Question for Bob:** the fix is to give `STADIUM_CAPACITY_TICK` a larger *threshold value*
|
`include/starkernel/vm/stadium.h` (comment only),
|
||||||
checked against the existing `fleet_heartbeat_tick_count` (still one clock, one counter —
|
`src/starkernel/capsule/capsule_vm_physics.c`. Three-arch QEMU acceptance, all clean to
|
||||||
just a bigger divisor, e.g. scaled by the live VM count so it restores something closer to
|
`ok>`, identical Stadium conservation invariant on all three
|
||||||
the required 1000:1 separation), not a second tick source. Wire it in that way, or resolve
|
(`resident_sum=43691 reservoir=21845 sum=65536`): `logs/20260815-093425/amd64/`,
|
||||||
the ordering some other way?
|
`logs/20260815-093521/aarch64/`, `logs/20260815-093641/riscv64/`.
|
||||||
|
|
||||||
**Closed-by-ruling, not reopened this pass** (each already has a recorded "flag and leave"
|
**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
|
decision from Captain Bob — reversing that without being asked would be exactly the
|
||||||
|
|||||||
+18
-10
@@ -64,17 +64,25 @@ config STADIUM_CONTAINS_DEPTH_MAX
|
|||||||
patrons held inside patrons within one Stadium.
|
patrons held inside patrons within one Stadium.
|
||||||
|
|
||||||
config STADIUM_CAPACITY_TICK
|
config STADIUM_CAPACITY_TICK
|
||||||
int "Capacity arbitration cadence, in virtual ticks (STADIUM_CAPACITY_TICK)"
|
int "Fleet transfer-slope re-estimation cadence, in virtual ticks (STADIUM_CAPACITY_TICK)"
|
||||||
default 1000
|
default 4000
|
||||||
help
|
help
|
||||||
How often Hera re-evaluates capacity transfers between VMs
|
How often vm_physics_heartbeat_tick() re-fits the fleet's
|
||||||
(FABRIC.md item 1.4), expressed in virtual ticks -- never
|
transfer-slope estimate (vm_physics_tick() -- a passive median
|
||||||
wall-clock. Default matches the existing precedent at
|
recompute over recent touch samples, not a capacity/transfer
|
||||||
capsule_vm_physics.c's vm_physics_heartbeat_tick()
|
decision itself), expressed in virtual ticks -- never wall-clock.
|
||||||
(HEARTBEAT_INFERENCE_FREQUENCY, also 1000), comfortably past
|
Wired in 2026-08-15 (FABRIC-2.md F.2/§12 Q5): this counter is fed
|
||||||
the "order of magnitude apart from the heat tick" minimum
|
by EVERY live VM's own vm_tick(), not one VM's, so it previously
|
||||||
(FABRIC.md §22.4). No consumer yet -- capacity arbitration
|
shared HEARTBEAT_INFERENCE_FREQUENCY (1000) and fired roughly
|
||||||
itself is not yet on the punch list.
|
(live VM count) times faster than a single VM's own heat-inference
|
||||||
|
gate -- backwards from the "order of magnitude apart" minimum
|
||||||
|
(FABRIC.md §22.4). Default of 4000 is a flat, untuned placeholder
|
||||||
|
picked to roughly restore that separation at Tripod's known
|
||||||
|
4-VM topology (Hera + two Hermes + Artemis) -- not computed from
|
||||||
|
live VM count at runtime, deliberately: a fixed constant, same
|
||||||
|
as every other frequency knob here, not adaptive logic. Real
|
||||||
|
tuning is DoE work (item 5.1), same treatment as the other
|
||||||
|
placeholder constants in this file.
|
||||||
|
|
||||||
config STADIUM_MEMORY_PERCENT
|
config STADIUM_MEMORY_PERCENT
|
||||||
int "Percent of free physical memory the Stadium claims at boot (STADIUM_MEMORY_PERCENT)"
|
int "Percent of free physical memory the Stadium claims at boot (STADIUM_MEMORY_PERCENT)"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Capsule Block Manifest — Auto-generated
|
# Capsule Block Manifest — Auto-generated
|
||||||
<!-- Generated by mkcapsule --manifest 2026-08-15T12:09:24Z -->
|
<!-- Generated by mkcapsule --manifest 2026-08-15T13:36:13Z -->
|
||||||
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
|
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
|
||||||
<!-- Hand-written justifications and immutability notes live -->
|
<!-- Hand-written justifications and immutability notes live -->
|
||||||
<!-- in MANIFEST.md alongside this auto-generated index. -->
|
<!-- in MANIFEST.md alongside this auto-generated index. -->
|
||||||
|
|||||||
Binary file not shown.
@@ -72,7 +72,7 @@
|
|||||||
#define STARFORTH_CONFIG_HEARTBEAT_INFERENCE_FREQUENCY_DEFAULT 1000
|
#define STARFORTH_CONFIG_HEARTBEAT_INFERENCE_FREQUENCY_DEFAULT 1000
|
||||||
#define STARFORTH_CONFIG_STADIUM_VM_MEMORY_PERCENT_DEFAULT 50
|
#define STARFORTH_CONFIG_STADIUM_VM_MEMORY_PERCENT_DEFAULT 50
|
||||||
#define STARFORTH_CONFIG_STADIUM_CONTAINS_DEPTH_MAX_DEFAULT 5
|
#define STARFORTH_CONFIG_STADIUM_CONTAINS_DEPTH_MAX_DEFAULT 5
|
||||||
#define STARFORTH_CONFIG_STADIUM_CAPACITY_TICK_DEFAULT 1000
|
#define STARFORTH_CONFIG_STADIUM_CAPACITY_TICK_DEFAULT 4000
|
||||||
#define STARFORTH_CONFIG_STADIUM_MEMORY_PERCENT_DEFAULT 1
|
#define STARFORTH_CONFIG_STADIUM_MEMORY_PERCENT_DEFAULT 1
|
||||||
#define STARFORTH_CONFIG_STADIUM_WORD_HEAT_QUANTUM_DEFAULT 2048
|
#define STARFORTH_CONFIG_STADIUM_WORD_HEAT_QUANTUM_DEFAULT 2048
|
||||||
#define STARFORTH_CONFIG_STADIUM_WORD_COOL_RATE_Q48_DEFAULT 21845
|
#define STARFORTH_CONFIG_STADIUM_WORD_COOL_RATE_Q48_DEFAULT 21845
|
||||||
|
|||||||
@@ -109,10 +109,12 @@ typedef char stadium_cell_size_check[(sizeof(StadiumCell) == STADIUM_CELL_BYTES)
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Items 1.1 and 1.4 named this item as where their Kconfig symbols would be
|
* Items 1.1 and 1.4 named this item as where their Kconfig symbols would be
|
||||||
* implemented. Neither has a consumer yet (item 3.5 for the depth cap,
|
* implemented. STADIUM_CONTAINS_DEPTH_MAX still has no consumer (item 3.5
|
||||||
* capacity arbitration -- not yet on the punch list -- for the tick); these
|
* for the depth cap, not yet implemented). STADIUM_CAPACITY_TICK was wired
|
||||||
* checks only prove the symbols are defined and sane, the same discipline
|
* in 2026-08-15 (capsule_vm_physics.c's vm_physics_heartbeat_tick(), see
|
||||||
* already applied to the byte-count checks above.
|
* FABRIC-2.md F.2/§12 Q5) -- this check now proves a real, live constant
|
||||||
|
* is sane, not just a placeholder, same discipline already applied to the
|
||||||
|
* byte-count checks above.
|
||||||
*/
|
*/
|
||||||
typedef char stadium_contains_depth_configured_check[(STADIUM_CONTAINS_DEPTH_MAX > 0) ? 1 : -1];
|
typedef char stadium_contains_depth_configured_check[(STADIUM_CONTAINS_DEPTH_MAX > 0) ? 1 : -1];
|
||||||
typedef char stadium_capacity_tick_configured_check[(STADIUM_CAPACITY_TICK > 0) ? 1 : -1];
|
typedef char stadium_capacity_tick_configured_check[(STADIUM_CAPACITY_TICK > 0) ? 1 : -1];
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -39,7 +39,7 @@
|
|||||||
#include "starkernel/kmalloc.h"
|
#include "starkernel/kmalloc.h"
|
||||||
#include "starkernel/q48_16.h"
|
#include "starkernel/q48_16.h"
|
||||||
#include "starkernel/console.h"
|
#include "starkernel/console.h"
|
||||||
#include "starforth_config.h" /* HEARTBEAT_INFERENCE_FREQUENCY */
|
#include "starforth_config.h" /* STADIUM_CAPACITY_TICK */
|
||||||
#include "starkernel/vm/bootstrap/sk_vm_bootstrap.h" /* sk_get_mama_vm */
|
#include "starkernel/vm/bootstrap/sk_vm_bootstrap.h" /* sk_get_mama_vm */
|
||||||
#include "vm.h" /* VM, vm->ssm_l8_state */
|
#include "vm.h" /* VM, vm->ssm_l8_state */
|
||||||
#include "ssm_jacquard.h" /* ssm_l8_state_t, CDConfigTable */
|
#include "ssm_jacquard.h" /* ssm_l8_state_t, CDConfigTable */
|
||||||
@@ -155,7 +155,10 @@ static uint64_t slope_fit_quality_q48 = 0;
|
|||||||
* without a forward declaration -- it is now this counter's second
|
* without a forward declaration -- it is now this counter's second
|
||||||
* reader, alongside vm_physics_heartbeat_tick()'s own gating use. */
|
* reader, alongside vm_physics_heartbeat_tick()'s own gating use. */
|
||||||
static uint64_t fleet_heartbeat_tick_count = 0;
|
static uint64_t fleet_heartbeat_tick_count = 0;
|
||||||
static uint64_t fleet_last_inference_tick = 0;
|
static uint64_t fleet_last_capacity_tick = 0; /* renamed 2026-08-15 from
|
||||||
|
fleet_last_inference_tick -- gated on
|
||||||
|
STADIUM_CAPACITY_TICK now, not HEARTBEAT_
|
||||||
|
INFERENCE_FREQUENCY, see vm_physics_heartbeat_tick() */
|
||||||
|
|
||||||
static vm_physics_node_t *vm_physics_find(VMUuid vm_id)
|
static vm_physics_node_t *vm_physics_find(VMUuid vm_id)
|
||||||
{
|
{
|
||||||
@@ -454,12 +457,22 @@ void vm_physics_tick(uint64_t now_ns)
|
|||||||
fleet_transfer_slope_q48 = (rates[rate_count / 2] * l8_regime_modulation_q16()) >> 16;
|
fleet_transfer_slope_q48 = (rates[rate_count / 2] * l8_regime_modulation_q16()) >> 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Gated on STADIUM_CAPACITY_TICK, not HEARTBEAT_INFERENCE_FREQUENCY (fixed
|
||||||
|
* 2026-08-15, FABRIC-2.md §12 Q5): fleet_heartbeat_tick_count is fed by
|
||||||
|
* EVERY live VM's own vm_tick() (see the comment above this counter's
|
||||||
|
* declaration), so it advances several times faster in wall-clock terms
|
||||||
|
* than a single VM's own tick_count -- comparing it against
|
||||||
|
* HEARTBEAT_INFERENCE_FREQUENCY (written assuming a single VM's stream)
|
||||||
|
* meant this fired far more often than §22.4's required ~1000:1 separation
|
||||||
|
* from a VM's own heat-inference loop. Still one clock, one counter --
|
||||||
|
* this is a bigger flat threshold on the same counter, not a second tick
|
||||||
|
* source or anything that reads live VM count to adjust itself. */
|
||||||
void vm_physics_heartbeat_tick(uint64_t now_ns)
|
void vm_physics_heartbeat_tick(uint64_t now_ns)
|
||||||
{
|
{
|
||||||
fleet_heartbeat_tick_count++;
|
fleet_heartbeat_tick_count++;
|
||||||
if ((fleet_heartbeat_tick_count - fleet_last_inference_tick) >= HEARTBEAT_INFERENCE_FREQUENCY) {
|
if ((fleet_heartbeat_tick_count - fleet_last_capacity_tick) >= STADIUM_CAPACITY_TICK) {
|
||||||
vm_physics_tick(now_ns);
|
vm_physics_tick(now_ns);
|
||||||
fleet_last_inference_tick = fleet_heartbeat_tick_count;
|
fleet_last_capacity_tick = fleet_heartbeat_tick_count;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user