Stage C-a: work law — running VM charged at each of its own beats, with carry (FABRIC-3.8 §3.13.2)

vm_physics_charge(): per-beat slope into a per-VM carry, whole units pulled
from the rest of the live fleet in proportion to heat (conservative
vm_physics_transfer), rounding shortfall kept in the carry, no fleet_window
sample. Called from vm_tick() only for the switch-current VM. Closes G9:
a VM running its own mail on its own stack now heats up while it works.

Probe (reverted): baseline Hestia heat 0->0->0 over 71 beats (FAIL, proves
G9); with charge 0->11->23 at beats 15->51->86, identical on amd64/aarch64/
riscv64, conserved, PASS. Acceptance: dict_hash unchanged, WIREBIND relay 3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-09-23 16:51:09 -04:00
co-authored by Claude Opus 5.5
parent 29447abb7d
commit 7c3736841d
19 changed files with 64261 additions and 6 deletions
+57 -5
View File
@@ -529,10 +529,13 @@ switch-current. Hera's is seeded at `sk_vm_switch_set_current()`, so her first c
cover every beat since boot. The clamp to `others_total` would make that harmless anyway, but it
should be defined rather than left to the clamp.
**What a beat counts.** A beat is 256 of *either* colon-word entries or words parsed at top level
by `vm_interpret` (the two `check_counter` sites). Primitives executed inside colon bodies are
**not** counted. So the "words" figures below are beat counts × 256 in that sense, not total
primitive executions.
**What a beat counts** (corrected at the start of C-a, from reading `vm_core.c:909` in context).
- A beat is every 256 words a VM executes. That counts each word dispatched inside a colon body,
primitives included (the `check_counter` bump in `execute_colon_word`'s inner loop), plus each
word parsed at top level by `vm_interpret` (`:1286`).
- The earlier draft said primitives inside colon bodies weren't counted. That was wrong.
- The "words" figures below are therefore beat counts × 256 executed words.
**Hera's reservoir mass (ruling C-4, open, arithmetic shown).**
@@ -540,7 +543,7 @@ primitive executions.
added to a closed system").
- At slope 1/3 raw per beat, the workers together have to produce about `65536 × 3 × (N/(N+1))`
beats before Hera cools to a worker's level. For N = 8 that is ≈175k beats, ≈45M
colon-entries/top-level words (see "What a beat counts" above).
executed words (see "What a beat counts" above).
- Until then, Hera is never the coolest while any worker has work, **so the console gets no turn
under load.** This doesn't block Stage C's relay evidence, because the relay runs while the
workers are idle. It does block Stage F/H usability.
@@ -827,3 +830,52 @@ explicit instruction.
and zero refused interprets**. The `dict_hash` set is identical across ISAs and unchanged
from Stages A/B. The full mint → WIREBIND → `USE` → `1 2 + .` → `3` relay works (the path
with a nested frame on Hera's stack). Clean build on all three plus hosted, no new warnings.
- [x] **Stage C-a — the work law: the running VM is charged at each of its own beats, with carry
(§3.13.2).** 2026-09-23. Captain Bob: *"commit it and start stage C-a"*, which accepts
§3.13.2's proposals C-1(a) and C-2 as written: charge at each beat, keep a carry, record no
`fleet_window` samples. C-3, C-4 and C-5 are C-b's rulings and are still open.
Changes:
- `capsule_vm_physics.c` gets a new `vm_physics_charge(vm_id)`. It adds one beat's slope
to a new per-VM `charge_carry`, then pulls the whole units from the rest of the live
fleet in proportion to each VM's heat, through `vm_physics_transfer()`.
- Clamped to what the others hold. That shortfall is dropped, as in touch, so the carry
can't grow without bound while one VM runs alone.
- Units lost to the proportional split's rounding go back into the carry.
- No `fleet_window` sample and no `last_active_tick` change, so `vm_physics_touch()` and
`VM-EXEC` behave byte-for-byte as before.
- `vm_runtime.c`'s `vm_tick()` calls the charge right after `vm_physics_heartbeat_tick()`,
only when the VM producing the beat is `sk_vm_switch_current_vm()`. A VM running nested
inside someone else's `VM-EXEC` stays on the dispatch law.
- §3.13.2 "What a beat counts" is corrected. `vm_core.c:909` is in `execute_colon_word`'s
inner loop, so primitives inside colon bodies *are* counted.
**Evidence, as named in §4.** A temporary probe (`kernel_main.c`: `SB-GO`/`SB-BACK` plus
`SB-HEAT`, which records Hestia's heat, the rest of the fleet's heat, her beat count and
`vm_physics_conserved()`; reverted with `git checkout`). It sends Hestia `: SBW ; : SBL 3000
0 DO SBW LOOP ; SB-HEAT SBL SB-HEAT SBL SB-HEAT SB-BACK`, and Hera runs `SB-GO SB-GO`.
- **Baseline (pre-change code, probe only):** `logs/20260923-160929/amd64/` gives
`hestia_heat 0 → 0 → 0` over beats 15 → 51 → 86, and **FAIL**. Hestia ran 71 beats of
her own mail on her own stack and gained nothing, which **proves G9**.
- **With the charge, all three ISAs:** `logs/20260923-161448/amd64/`,
`-161959/aarch64/` and `-162543/riscv64/` give, byte-identically, `hestia_heat 0 → 11 →
23` at beats 15 → 51 → 86, `rest 65536 → 65525 → 65513`, conserved at every sample, and
**PASS**. The heat rises **mid-turn** at each in-turn sample.
- Exact to the arithmetic: switched in at beat 15, so ⌊36·21845/65536⌋ = 11 and
⌊71·21845/65536⌋ = 23. Every unit Hestia gained came out of the rest of the fleet (all
from Hera; Artemis held 0).
- `fleet_window_warm=NO` and `slope 21845` are unchanged. By source, the charge never
writes `fleet_window`.
**Formal acceptance with the probe gone,** coupled images reset before each:
`logs/20260923-163215/amd64/`, `-163757/aarch64/`, `-164359/riscv64/`.
- All reach `[zuse@Hera] ok>` with zero `UNKNOWN WORD`, failures, faults, `VM FRAME
CORRUPTION` and refused interprets.
- The `dict_hash` set is identical across ISAs and unchanged from Stages A/B/FR.
- The full mint → WIREBIND → `USE` → `1 2 + .` → `3` relay works, with
`stadium_conserved(self)=true`.
- Clean build on all three plus hosted, no new warnings.
**Consequence to watch in C-b:** heat now moves at boot too. Hera is switch-current from
her Stage 3 registration onward and is charged for her own beats. She holds nearly all the
heat, so her charges pull almost nothing; that is the C-4 arithmetic playing out as
expected. The per-beat DoE heat columns will read differently from pre-C-a runs.