starkernel: restate VM fleet heat transfer on the virtual tick

Punch list §25 item 2.1 complete.
vm_physics_touch() no longer takes a wall-clock timestamp -- it reads
fleet_heartbeat_tick_count internally, which is execution-paced
(vm_runtime.c:143), not wall-clock. VMPhysics.last_active_ns ->
last_active_tick, VMFleetTouchSample.elapsed_us -> elapsed_ticks, and a
new explicit `touched` flag replaces the old `> 0` sentinel (tick 0 is
a legitimate value a first touch can land on, unlike wall-clock ns).

Verified: three-architecture boot (amd64 x2, aarch64, riscv64), all
reaching ok> with identical dict_hash=0x3d4e1daf289da94f matching the
item-0.10 baseline. No new compiler warnings in the touched files.

Honestly flagged, not fixed: with Tripod pruned to Hera alone (item
0.1), vm_physics_touch()'s fan-out has no other live VM to pull heat
from, so the fleet-heat-sum acceptance criterion is trivially satisfied
rather than genuinely stress-tested -- a real check needs Phase 4's
multi-VM fleet. fleet_transfer_slope_q48's seed (65536/3) was
calibrated for elapsed microseconds and has not been re-fit for elapsed
ticks; left as-is rather than guessed, deferred to item 5.1's DoE work.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-08-04 13:15:50 -04:00
co-authored by Claude Sonnet 5
parent f2eb877691
commit 542d7dbf0d
13 changed files with 41843 additions and 60 deletions
+75 -52
View File
@@ -58,7 +58,10 @@
typedef struct {
uint64_t execution_heat_q48;
uint64_t last_active_ns;
uint64_t last_active_tick; /* fleet_heartbeat_tick_count value at last touch */
int touched; /* 0 until the first touch; explicit rather than
* overloading last_active_tick == 0, which is a
* legitimate tick value a first touch can land on */
int is_live;
} VMPhysics;
@@ -72,24 +75,30 @@ typedef struct vm_physics_node {
* vm_registry_head/vm_registry_count -- unbounded, not a fixed array. */
static vm_physics_node_t *vm_physics_head = (void *)0;
/* One recorded touch's inputs to the transfer law (amount = elapsed_us *
/* One recorded touch's inputs to the transfer law (amount = elapsed_ticks *
* slope >> 16, clamped to available heat) -- not a derived heat value.
* Rev o (VM-FLEET-ATTRACTOR-DESIGN-20260705.md) replaced the earlier
* heat-snapshot design (rev m) with this: since the transfer law is known
* exactly, the true rate can be recovered directly from any touch that
* wasn't clamped (rate = amount / elapsed_us), rather than curve-fitting
* a reconstructed trajectory against a synthetic time axis. */
* wasn't clamped (rate = amount / elapsed_ticks), rather than curve-fitting
* a reconstructed trajectory against a synthetic time axis.
*
* Restated on the virtual tick (FABRIC.md item 2.1, 2026-08-04): this used
* to be elapsed_us, wall-clock microseconds -- non-reproducible under TCG
* per GAP-A1 (FABRIC.md §25.7.1). Now counts fleet_heartbeat_tick_count
* ticks, which is execution-paced (incremented once per vm_tick() call,
* confirmed at vm_runtime.c:143). */
typedef struct {
uint64_t elapsed_us; /* since this vm_id's previous touch; 0 if there
* was no previous touch to compare against */
uint64_t amount; /* elapsed_us * slope >> 16 at touch time, i.e.
* the rate-implied transfer before any clamp;
* 0 if not computed (first touch, zero slope,
* or non-monotonic clock) */
int clamped; /* 1 if the heat actually moved was capped below
* `amount` by what the rest of the fleet held --
* carries no rate information, excluded from
* the estimator rather than treated as a zero */
uint64_t elapsed_ticks; /* since this vm_id's previous touch; 0 if there
* was no previous touch to compare against */
uint64_t amount; /* elapsed_ticks * slope >> 16 at touch time,
* i.e. the rate-implied transfer before any
* clamp; 0 if not computed (first touch, zero
* slope, or non-monotonic tick) */
int clamped; /* 1 if the heat actually moved was capped below
* `amount` by what the rest of the fleet held --
* carries no rate information, excluded from
* the estimator rather than treated as a zero */
} VMFleetTouchSample;
typedef struct {
@@ -119,15 +128,35 @@ static VMFleetWindow fleet_window = { { { 0, 0, 0 } }, 0, 0, 0 };
* in include/vm.h ("starts at 2:1 = 131072") is stale relative to the real
* code -- 1/3 is what's actually seeded there, so 1/3 is what's mirrored
* here. This was previously seeded at literal 0, which cannot bootstrap:
* vm_physics_touch's transfer amount is elapsed_ns * slope >> 16, so a
* vm_physics_touch's transfer amount is elapsed_ticks * slope >> 16, so a
* zero slope moves zero heat on every touch forever, which starves
* vm_physics_tick's regression of the very heat-trajectory signal it
* needs to infer a better slope -- a closed loop with no way out. Found
* via doe_log.c's per-VM heat CSV columns showing a dead-flat trajectory
* across a full boot (VM-FLEET-ATTRACTOR-DESIGN-20260705.md rev f). */
* across a full boot (VM-FLEET-ATTRACTOR-DESIGN-20260705.md rev f).
*
* UNVALIDATED after the ns-to-tick restatement (FABRIC.md item 2.1,
* 2026-08-04): this value was calibrated against elapsed wall-clock
* microseconds. Elapsed fleet-heartbeat *ticks* between touches is a
* different quantity at a different scale, and the seed has not been
* re-fit against it. Left as-is rather than guessed at -- the acceptance
* boot test below only checks determinism, not transfer magnitude; a real
* re-tune is DoE work (item 5.1), not something to invent here. */
static uint64_t fleet_transfer_slope_q48 = 65536ULL / 3;
static uint64_t slope_fit_quality_q48 = 0;
/* Fleet-wide heartbeat tick, distinct from any VM's own per-VM
* HeartbeatState.tick_count -- see the rationale in the header. Advances
* from whichever VM's own heartbeat cycle happens to fire, so the
* readiness signal reflects the fleet's aggregate activity rather than
* any one VM's (in practice, Hera's) personal word-execution rate.
* Declared here (rather than just above vm_physics_heartbeat_tick, where
* it used to live) so vm_physics_touch() can read the current tick
* without a forward declaration -- it is now this counter's second
* reader, alongside vm_physics_heartbeat_tick()'s own gating use. */
static uint64_t fleet_heartbeat_tick_count = 0;
static uint64_t fleet_last_inference_tick = 0;
static vm_physics_node_t *vm_physics_find(uint32_t vm_id)
{
vm_physics_node_t *n = vm_physics_head;
@@ -168,7 +197,8 @@ void vm_physics_init(uint32_t vm_id)
if (node) {
node->physics.execution_heat_q48 = is_root ? Q48_ONE : 0;
node->physics.last_active_ns = 0;
node->physics.last_active_tick = 0;
node->physics.touched = 0;
node->physics.is_live = 1;
return;
}
@@ -178,7 +208,8 @@ void vm_physics_init(uint32_t vm_id)
node->vm_id = vm_id;
node->physics.execution_heat_q48 = is_root ? Q48_ONE : 0;
node->physics.last_active_ns = 0;
node->physics.last_active_tick = 0;
node->physics.touched = 0;
node->physics.is_live = 1;
node->next = vm_physics_head;
vm_physics_head = node;
@@ -247,33 +278,32 @@ void vm_physics_retire(uint32_t vm_id)
dying->physics.is_live = 0;
}
void vm_physics_touch(uint32_t vm_id, uint64_t now_ns)
void vm_physics_touch(uint32_t vm_id)
{
vm_physics_node_t *target = vm_physics_find(vm_id);
VMFleetTouchSample sample = { 0, 0, 0 };
vm_physics_node_t *target = vm_physics_find(vm_id);
VMFleetTouchSample sample = { 0, 0, 0 };
uint64_t now_tick = fleet_heartbeat_tick_count;
if (!target || !target->physics.is_live) return;
/* Restated on the virtual tick (FABRIC.md item 2.1, 2026-08-04): this
* used to gate on target->physics.last_active_ns > 0 and scale by
* elapsed wall-clock microseconds -- non-reproducible run to run under
* TCG (GAP-A1, FABRIC.md §25.7.1). fleet_heartbeat_tick_count is
* execution-paced (vm_runtime.c:143), so elapsed_ticks is now a pure
* function of the execution stream. `touched` replaces the old
* `> 0` sentinel: tick 0 is a legitimate value for a genuine first
* touch to land on, so "never touched" needs its own flag rather than
* overloading the tick value. */
if (fleet_transfer_slope_q48 > 0 &&
target->physics.last_active_ns > 0 &&
now_ns > target->physics.last_active_ns) {
target->physics.touched &&
now_tick > target->physics.last_active_tick) {
/* Microseconds, not nanoseconds, matching the word engine's own
* convention (physics_metadata_apply_linear_decay,
* physics_metadata.c) that fleet_transfer_slope_q48's seed value
* was explicitly calibrated to mirror (see the comment on the
* static above). Using raw elapsed_ns here was an outright unit
* bug, not a design choice: real touch-to-touch gaps run to
* milliseconds, so (elapsed_ns * slope_q48) >> 16 overshot the
* fleet's entire conserved heat (Q48_ONE) by more than 10x on the
* very first post-seed touch, clamping to a full winner-take-all
* transfer in a single step instead of the gradual pull this was
* meant to model (VM-FLEET-ATTRACTOR-DESIGN-20260705.md rev n). */
uint64_t elapsed_us = (now_ns - target->physics.last_active_ns) / 1000;
uint64_t amount = (elapsed_us * fleet_transfer_slope_q48) >> 16;
uint64_t elapsed_ticks = now_tick - target->physics.last_active_tick;
uint64_t amount = (elapsed_ticks * fleet_transfer_slope_q48) >> 16;
sample.elapsed_us = elapsed_us;
sample.amount = amount;
sample.elapsed_ticks = elapsed_ticks;
sample.amount = amount;
if (amount > 0) {
/* Pull toward the touched VM, proportional to each other LIVE
@@ -310,7 +340,8 @@ void vm_physics_touch(uint32_t vm_id, uint64_t now_ns)
}
}
target->physics.last_active_ns = now_ns;
target->physics.last_active_tick = now_tick;
target->physics.touched = 1;
/* Record this touch's transfer-law inputs, not a derived heat value
* (rev o, VM-FLEET-ATTRACTOR-DESIGN-20260705.md) -- see
@@ -371,7 +402,7 @@ void vm_physics_tick(uint64_t now_ns)
(void)now_ns; /* Unlike the log-linear regression this replaced, the
* estimator below needs no external time reference --
* each sample already carries its own elapsed_us. */
* each sample already carries its own elapsed_ticks. */
/* Mirrors vm_tick_inference_engine's is_warm gate: skip rather than
* substitute a default slope. Expected to stay unwarmed (or warm
@@ -382,17 +413,17 @@ void vm_physics_tick(uint64_t now_ns)
/* Direct rate recovery, not curve-fitting (rev o,
* VM-FLEET-ATTRACTOR-DESIGN-20260705.md, superseding the log-linear
* OLS approach of rev b-n): the transfer law is known exactly
* (amount = elapsed_us * slope >> 16), so any unclamped touch can be
* inverted directly for an exact per-sample rate. Order doesn't
* (amount = elapsed_ticks * slope >> 16), so any unclamped touch can
* be inverted directly for an exact per-sample rate. Order doesn't
* matter here -- a median over the observation window, not a
* time-indexed fit -- so this walks the fixed-size array directly
* rather than replaying ring order from fleet_window.head. */
for (i = 0; i < VM_FLEET_WINDOW_DEPTH; i++) {
VMFleetTouchSample *s = &fleet_window.touch_samples[i];
if (s->clamped || s->amount == 0 || s->elapsed_us == 0) continue;
if (s->clamped || s->amount == 0 || s->elapsed_ticks == 0) continue;
rates[rate_count++] = (s->amount << 16) / s->elapsed_us;
rates[rate_count++] = (s->amount << 16) / s->elapsed_ticks;
}
/* slope_fit_quality_q48 as the informative fraction of the window --
@@ -423,14 +454,6 @@ void vm_physics_tick(uint64_t now_ns)
fleet_transfer_slope_q48 = (rates[rate_count / 2] * l8_regime_modulation_q16()) >> 16;
}
/* Fleet-wide heartbeat tick, distinct from any VM's own per-VM
* HeartbeatState.tick_count -- see the rationale in the header. Advances
* from whichever VM's own heartbeat cycle happens to fire, so the
* readiness signal reflects the fleet's aggregate activity rather than
* any one VM's (in practice, Hera's) personal word-execution rate. */
static uint64_t fleet_heartbeat_tick_count = 0;
static uint64_t fleet_last_inference_tick = 0;
void vm_physics_heartbeat_tick(uint64_t now_ns)
{
fleet_heartbeat_tick_count++;
+3 -3
View File
@@ -579,7 +579,7 @@ static void mama_word_vm_step(VM *vm)
return;
}
vm_physics_touch(entry.vm_id, vm_monotonic_ns(vm));
vm_physics_touch(entry.vm_id);
saved_name = console_get_vm_name();
console_set_vm_name(entry.name);
@@ -655,7 +655,7 @@ static void mama_word_vm_exec(VM *vm)
return;
}
vm_physics_touch(entry.vm_id, vm_monotonic_ns(vm));
vm_physics_touch(entry.vm_id);
log_message(LOG_INFO, "VM-EXEC: '%s' -> '%s'", cmd_buf, vm_name);
saved_name = console_get_vm_name();
@@ -736,7 +736,7 @@ static void mama_word_vm_call(VM *vm)
vm_push(vm, 0); return;
}
vm_physics_touch(entry.vm_id, vm_monotonic_ns(vm));
vm_physics_touch(entry.vm_id);
log_message(LOG_DEBUG, "VM-CALL: '%s' -> '%s'", cmd_buf, vm_name);
saved_name = console_get_vm_name();