FABRIC-3.8 §3.13: Stage C design — G9 (self-driven drains move no heat), per-beat charge with carry, decision, idle point; C split into C-a/C-b

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-09-23 16:06:59 -04:00
co-authored by Claude Opus 5.5
parent 6a313c7bf2
commit 29447abb7d
+312 -2
View File
@@ -382,6 +382,298 @@ Hera resuming while Hestia is still parked mid-payload. **Baseline first:** the
the pre-frame code must *fail* on that third entry (reading Hestia), proving the test detects the pre-frame code must *fail* on that third entry (reading Hestia), proving the test detects
the defect before the fix is claimed. the defect before the fix is claimed.
### 3.13 Stage C design: heat under self-driven execution, and the switch decision (FOR REVIEW, 2026-09-23)
§3.11 ruled *coolest VM with pending work goes next, no scheduler*. Before designing the
comparison, I traced (read-only) what that heat actually does once a VM runs its own mail on its
own stack. **The finding blocks Stage C as written.** Everything below is traced unless marked as
a proposal.
#### 3.13.1 What the trace found
**G9: when a VM runs its own mail, VM-physics heat doesn't move.**
- `vm_physics_touch()` (`capsule_vm_physics.c:284`) is the only thing that moves
`execution_heat_q48` between live VMs (apart from `retire`, which moves heat to the root on
kill). Its only callers are Hera's three orchestration verbs: `VM-STEP`, `VM-EXEC` and
`VM-CALL` (`mama_forth_words.c:723/811/946`). Each one touches the *target* at dispatch.
- `sk_hermes_drain_checkpoint()` and the trampoline (`switch.c`) call nothing in physics. A
VM that runs a kernel-Hermes payload on its own stack, which is the whole point of Stages
C–H, leaves every heat value exactly where it was.
- Starting state (`vm_physics_init:188`): Hera holds all of `Q48_ONE` (0x10000 raw) and every
other VM starts at 0.
- **Consequence:** once the DoE is rebuilt on `SEND` (§3.9), nothing ever calls `VM-EXEC`. Every
worker stays at 0 and Hera stays at `Q48_ONE` forever. "Coolest with work" becomes a permanent
tie among all workers, and whatever breaks that tie is the scheduler in all but name. That
violates *"no scheduler allowed."*
**The transfer law barely moves heat at switch granularity** (`vm_physics_touch`, lines
301–343):
- The formula is `amount = elapsed_beats × slope >> 16`, with slope seeded at `65536/3` = 21845
raw.
- `elapsed_beats` counts fleet beats since *this VM's own last touch*. Every touch resets it
(`last_active_tick = now`), and a VM's first-ever touch moves nothing (`touched == 0`).
- Worked out: elapsed 1, 2 or 3 moves **0**. 3 × 21845 = 65535, and 65535 >> 16 = 0. Elapsed 4
moves 1 raw unit (1/65536 of the fleet). Elapsed 100 moves 33 raw units, about 0.05%.
- The truncated remainder is thrown away, not carried. So touching often moves *nothing*, which
rules out the obvious "touch the running VM at each of its beats": elapsed would always be 1.
- The seed's own comment (lines 138–144) already says the slope is **"UNVALIDATED after the
ns-to-tick restatement… a real re-tune is DoE work (item 5.1)."**
**What a beat is, and that every VM produces them** (checked because Bob ruled out the timer):
- The fleet beat (`fleet_heartbeat_tick_count`) advances once per `vm_heartbeat_run_cycle()` →
`vm_tick()` → `vm_physics_heartbeat_tick()`.
- That runs every `HEARTBEAT_CHECK_FREQUENCY` (256, Kconfig) words of **a VM's own execution**.
The per-VM `check_counter` is bumped at both `vm_core.c:909` (each colon word) and `:1286`
(each word parsed by `vm_interpret`).
- `heartbeat_enabled = 1` and `worker = NULL` are set for every VM in `vm_init`
(`vm_bootstrap.c:305/308`). The pthread worker path is hosted-only.
- **So the beat is execution-paced, produced by whichever VM is running, and not the timer.**
The *timer* ISR is a different function, `heartbeat_tick()` in `heartbeat.c`. Today it calls
`sk_vm_switch_signal_tick()` (`heartbeat.c:179`), which is the readiness path Bob ruled out.
In this doc, "beat" always means the execution-paced one.
- Each VM also keeps its own beat count, `vm->heartbeat.tick_count` (`vm_runtime.c:118`), so
the beats a VM produced during one turn can be read directly.
**Stadium heat doesn't answer "whose turn" either.**
- A message's Stadium heat is pulled from the **sender's** reservoir at `sk_hermes_alloc()`, and
the cell's owner is the sender. On release it goes back to the **sender** (`stadium_evict()`).
- The recipient's Stadium state never changes when it receives or drains.
- `sk_hermes_decay()` is only called from the boot self-tests (`kernel_main.c:969/1006/1033`).
- So Stadium heat measures a VM's *outstanding sent mail*. It says nothing about how much CPU a
recipient has had. It's listed for completeness, not proposed.
**Stage C reverses a recorded decision.** `capsule_vm_switch_signal.h:28–34` deliberately did
*not* use VM-physics heat for switching, on timescale grounds: word-level dispatch over millions
of executions versus instant-by-instant readiness. Bob's §3.11 ruling overrides it. The
truncation above is that timescale gap in concrete form, and §3.13.2 is how it would be closed.
**The existing law measures age, not work** (derived from the code, worth stating because it
changes what "coolest" means):
- A touch pulls in proportion to beats *since the last touch*.
- Over a VM's life, the sum of its pulls is its age in beats × slope, however often or rarely it
actually ran. How often it ran only changes *when* it gets charged.
- It loses heat when others are touched, in proportion to its share.
- So under the current law, "coolest" ranks VMs by charge timing and share, not by CPU used.
#### 3.13.2 Proposal: charge a VM for the beats it produced (ruling C-1)
**Proposed: charge the running VM once at each of its own beats, keeping a carry.**
- At every beat a VM produces while it is the switch-current VM, it is charged one beat's worth:
`slope` raw units, plus the carry left over from its previous beats.
- The whole units are pulled from the rest of the live fleet in proportion to each VM's heat,
through the existing conservative `vm_physics_transfer()`. Nothing is created, and fleet K is
still conserved.
- The fraction below one unit stays in a per-VM carry. That carry is what makes a per-beat charge
work where a per-beat *touch* can't (see row (d)).
- It would be one new entry point in `capsule_vm_physics.c`, e.g. `vm_physics_charge(vm_id)`,
called from the beat path (`vm_physics_heartbeat_tick()`'s caller) for the running VM.
- It is kernel-side and invisible to anything a VM executes.
- Result: **the VM that has done the most work is the hottest,** and its heat rises *while it
runs*. That matches the plain meaning of "coolest VM with work goes next."
**Why per beat and not at switch-out (corrected before review).** An earlier draft of this
section charged only at switch-out. That design is **purely cooperative**, which contradicts
§3.11:
- During a turn nothing would change. The running VM's stored heat stays where it was at
switch-in, and the waiting VMs are untouched.
- So every decision taken during the turn repeats the answer from switch-in. The running VM is
never displaced, and a long payload keeps the CPU until it finishes.
- Charging per beat is what makes the running VM's own execution displace it, which is the
preemptive effect.
**Worked example.** Two workers start at 0 with equal work.
- The running worker gains 21845/65536 of a unit per beat, so a whole unit moves every 3 beats.
- At that point the other worker is strictly cooler and takes over at the next beat decision.
- They alternate every ~3–6 beats. The turn length comes out of the physics; nobody chose it.
| Option | Moves heat under self-driven execution? | Preemptive in effect? | Side effects |
|---|---|---|---|
| **(a) Charge at each of the running VM's own beats, with carry** (proposed) | Yes, in proportion to work | **Yes**, because heat rises during the turn | New entry point. Proposed: records **no** `fleet_window` samples (see "Frozen slope" below). |
| (a′) Charge at switch-out by `tick_count` delta | Yes, in proportion to work | **No**, unless decisions compare *projected* heat: stored heat plus the charge for the beats produced so far this turn. With projection it is equivalent to (a), but without any mid-turn transfer. | Same as (a). |
| (b) `vm_physics_touch()` on switch-in (the `VM-EXEC` analogue) | Yes, but it charges *time away*, not work (see "age, not work"), and needs ≥4 beats away before anything moves | No | Writes `fleet_window` samples, which changes slope adaptation and comparability with existing DoE heat columns. |
| (d) `vm_physics_touch()` at each beat | **No.** Elapsed is always 1 and the remainder is discarded, so 0 moves, forever | — | Rejected on the arithmetic. Row (a) differs only by keeping the carry and not resetting the elapsed count. |
**Truncation (ruling C-2).** With the carry, nothing is lost to rounding under (a). The other
choices:
- (i) no carry: then (a) moves nothing, the same as (d);
- (iii) refit the slope at beat scale. That is DoE item 5.1 and **out of scope** here.
The carry applies to the new charge only. `vm_physics_touch()` is untouched, so `VM-EXEC`
transfers stay byte-for-byte as today.
**Frozen slope (part of C-1).**
- With no samples recorded by the charge, and no `VM-EXEC` in the rebuilt DoE, the adaptive
`fleet_transfer_slope_q48` **never updates under self-driven load**. It stays at the
unvalidated seed.
- Recording samples would let it adapt. It would also change what the existing heat columns mean
compared with past campaigns.
- Bob should choose this knowingly.
**Two laws on one ledger.** Artemis, and any participant that Hera also `VM-EXEC`s, is both
*touched* (the dispatch law) and *charged* (the work law). Both go through `vm_physics_transfer()`
on the same heat, so conservation holds, but the VM's heat then mixes two meanings. Proposed:
accept this and document it. `VM-EXEC` into a participant becomes rare once §3.8's `SEND` exists.
**Where the charge starts.** Each VM's charge baseline is its beats at the moment it becomes
switch-current. Hera's is seeded at `sk_vm_switch_set_current()`, so her first charge doesn't
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.
**Hera's reservoir mass (ruling C-4, open, arithmetic shown).**
- Hera starts with all of `Q48_ONE` as the fleet's structural root (`vm_physics_init`'s "cold mass
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).
- 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.
- Options for Bob:
- (α) accept it;
- (β) compare the heat each VM has been *charged* (its turn heat) rather than its absolute
heat, so the root's structural mass doesn't count against it;
- (γ) something else Bob rules.
- I have no recommendation yet. (β) is a different physical quantity, and Bob should choose it
knowingly.
#### 3.13.3 Proposal: the decision (ruling C-3 on ties)
**Eligible** means the VM is a registered participant, is not the one running, is not dead or
stillborn, and **either** of the following holds:
- its kernel-Hermes pending count is > 0, read directly from the queue
(`sk_hermes_pending_count`), not from a `has_work` flag. The queue *is* the eligibility, so
`SWITCH-MARK-WORK` and G1's missing mark become unnecessary;
- it is switched out, its **frame stack is non-empty, and its top frame is not
`VM_FRAME_IDLE`**. That means it was displaced mid-task, or it is Hera with her REPL chain live.
- Without this clause, a VM preempted in the middle of a payload never gets picked again. This
is what the frame stacks were for.
- A never-entered VM has an empty stack, so it is not eligible unless it has mail.
- Hera parked at an idle point pushes an IDLE frame (§3.12, and §3.13.5 below), so she is not
eligible there unless she has mail or input.
**Switch when** some eligible VM is **strictly** cooler than the one running. If it is equal, the
running VM stays: a tie with the incumbent is "no reason to move," not a coin toss.
**Ties among several equally cool candidates (ruling C-3).**
- **Proposed:** the candidate that has been *waiting longest*, measured by one single-writer
event count.
- Each message is stamped with it when it is admitted (a new `SkHermesMessage` field).
- Each VM is stamped with it when it is displaced mid-task.
- A candidate's wait stamp is the minimum of its oldest pending mail and its displacement.
- Every stamp is unique, so the result is unique.
- This is the arrival order of the mail itself, the kernel's FIFO. It is a fact about the
messages, not a ranking of VMs.
- **Bob to rule** whether arrival order counts as "an ordering" under *"no scheduler allowed."*
- Rejected outright: slot index, UUID and registry order. Each of those is a fixed order of VMs.
**Testable "not a scheduler" criterion.**
- The decision is a pure function of physical state: heat, pending mail, frames and wait stamps.
It keeps no queue of its own and remembers nothing between decisions.
- It must return the same VM whichever order the participant table is walked.
- This is Stage C-b's evidence (below).
**When the decision is made** (only at moments the physics exposes, never on a clock):
1. **The running VM's own beat.** The beat sets a per-VM "decide" flag. The next *owned*
checkpoint consumes it (`sk_vm_owns_outermost_frame`). There are two checkpoint sites:
- the existing one in `execute_colon_word`;
- the between-words point in `vm_interpret_kind`'s loop, which needs a new gated checkpoint
so that a payload made only of primitives can be displaced.
2. **Mail arrival** to a VM that would then win.
3. **The trampoline's between-message point**, as already noted in `sk_vm_switch_entry()`.
4. **Idle points** (Stage F).
**Why not every word.** Under (a), heat changes only at beats, and eligibility changes only when
mail arrives or a queue empties. Between those events every decision would repeat the last one,
so deciding every word is pure overhead. Deciding at the beat means a turn lasts at least one beat
unless the VM runs out of work first.
**Flagged honestly:** that minimum is the existing Loop #7 cadence constant
(`HEARTBEAT_CHECK_FREQUENCY`). It is not new, but it does act as a floor on turn length.
**The switch target on an empty queue.**
- Today the trampoline yields to whoever switched in (`native_switch_back_to`).
- **Proposed:** it asks the decision instead, and falls back to `native_switch_back_to` only when
no VM is eligible.
#### 3.13.4 What removing the timer path changes
- `heartbeat.c:179`'s `sk_vm_switch_signal_tick()` call is removed. The ISR returns to its
pre-§XXVIII minimal shape, so the timer no longer decides anything.
- These are removed:
- the readiness accrual;
- `SK_SWITCH_READINESS_THRESHOLD` (50);
- `has_work`;
- `take_pending`.
- `note_switch_performed` is kept only as the counter.
- **Effect on the DoE CSV** (`doe_log.c:205–230`):
- `switch_count` and `switch_current_slot` survive.
- `switch_ticks_since` loses its meaning. Proposed: it becomes beats since the last switch.
- The three `*_readiness` columns die.
- Found while tracing, *not fixed*: those columns and the heat columns still look up
`"Hermes"`, which no longer exists as a VM since the reshuffle, so those values are always 0
today.
#### 3.13.5 The relay needs the idle point in the same stage (traced)
Stage C-b's own evidence, the WIREBIND relay delivered by a real switch, **can't be reached
without §3.7's idle point** (Stage F):
- The console line is forwarded from C. `sk_hermes_send_one(vm->stadium_vm_id, paired.vm_id,
CONSOLE_CMD, …)` at `repl.c:1510`, then `goto out`. Hera interprets nothing and goes straight
back to polling.
- Today the `~user` VM's mail is delivered by the idle pump (`repl.c:~909–928`), which drains
every live VM *on Hera's stack*. That is G3.
- Once the pump skips participants (C-b), the only places a switch can happen are owned word
checkpoints and beats. Hera at her poll loop reaches neither: she is running C, not words.
- So nothing switches, the `~user` VM's `CONSOLE-CMD-EVENT` sits in its queue, and the relay
breaks.
**Proposed:** fold §3.7's idle point into C-b.
- The pump loop's own location (the idle point, Hera between lines) becomes a decision point.
Hera pushes an IDLE frame, consults the decision, and switches to the coolest eligible VM
instead of draining participants on her stack. The pump then shrinks to Hera's own queue.
- Stage F then only has to show that work proceeds with the console idle.
- The alternative is to keep the pump draining participants until F. That keeps G3's
borrowed-stack delivery alive through C-b, so C-b's relay evidence would prove nothing about
switching.
**Hera's eligibility at the idle point.** Keyboard or serial input pending is Hera's mail. At
the idle point she is eligible when input is pending (the REPL's own input poll), exactly as a
worker is eligible when its kernel-Hermes queue is non-empty. **Ruling C-5**, below.
#### 3.13.6 Stage C, split
This splits the single C row in §4 in two:
- **C-a** is physics only: heat starts moving and nothing switches yet.
- **C-b** is the decision: pump skip, eligibility read from the queue, the ISR path removed, and
the idle point (§3.13.5).
C-a is observable on its own, and doing it first proves the heat signal is alive before anything
depends on it. The §4 table below is updated to show this split, **pending rulings C-1 to C-4.**
--- ---
## 4. Staged plan ## 4. Staged plan
@@ -395,10 +687,12 @@ healthy idle produce the same serial log (`FABRIC-3.md` §XXVIII.1).
| **A** | §3.1 per-VM ownership gate (+ `MODE_COMPILE` gate). Nothing else. | Byte-identical behavior: `dict_hash` triple unchanged, identical boot, WIREBIND mint→attach→`USE`→relay (the `FABRIC-3.6.md` Phase 4 acceptance sequence) still `1 2 + .` → `3`. | | **A** | §3.1 per-VM ownership gate (+ `MODE_COMPILE` gate). Nothing else. | Byte-identical behavior: `dict_hash` triple unchanged, identical boot, WIREBIND mint→attach→`USE`→relay (the `FABRIC-3.6.md` Phase 4 acceptance sequence) still `1 2 + .` → `3`. |
| **B** | §3.2 trampoline idle loop + §3.3 `hermes_draining` + §3.5 parked-idle flag. Still inert (nothing marks work yet). | Same as A: nothing can reach the new trampoline branch yet, so behavior is identical. A synthetic self-test (kernel_main.c, the §XXVIII Stage 2 `SWITCH-TEST` pattern: write, capture, revert) switches into **Hestia** (not Artemis, whom the Zuse attach flow `VM-EXEC`s; a parked VM refuses `VM-EXEC`) with **two** messages queued, the first calling a colon word so the in-word drain checkpoint is actually reached. The ordering witness `SB-SEQ` must equal 12 (one-at-a-time drain), not 21 (nested). She must end parked-idle at depth 0 with her context saved on her own stack, current VM back to Hera, and the ledger and `stadium_conserved()` restored. | | **B** | §3.2 trampoline idle loop + §3.3 `hermes_draining` + §3.5 parked-idle flag. Still inert (nothing marks work yet). | Same as A: nothing can reach the new trampoline branch yet, so behavior is identical. A synthetic self-test (kernel_main.c, the §XXVIII Stage 2 `SWITCH-TEST` pattern: write, capture, revert) switches into **Hestia** (not Artemis, whom the Zuse attach flow `VM-EXEC`s; a parked VM refuses `VM-EXEC`) with **two** messages queued, the first calling a colon word so the in-word drain checkpoint is actually reached. The ordering witness `SB-SEQ` must equal 12 (one-at-a-time drain), not 21 (nested). She must end parked-idle at depth 0 with her context saved on her own stack, current VM back to Hera, and the ledger and `stadium_conserved()` restored. |
| **FR** | §3.12 VM frames: `VMFrame` header; Stage A/B fields move into it; stack-scoped log attribution. | Probe `SB-ATTRIB`/`SB-GO`/`SB-BACK` (reverted): attribution sequence Hera, Hestia, **Hera**, Hestia on all three ISAs, and the **same probe fails on the pre-frame baseline** (third entry reads Hestia). Then formal acceptance: `dict_hash` unchanged, relay works. | | **FR** | §3.12 VM frames: `VMFrame` header; Stage A/B fields move into it; stack-scoped log attribution. | Probe `SB-ATTRIB`/`SB-GO`/`SB-BACK` (reverted): attribution sequence Hera, Hestia, **Hera**, Hestia on all three ISAs, and the **same probe fails on the pre-frame baseline** (third entry reads Hestia). Then formal acceptance: `dict_hash` unchanged, relay works. |
| **C** | §3.4 eligibility marked + pump skips participants (one commit). | The WIREBIND relay still works, now delivered by a real switch: `switch_count_cumulative` increments across the relay and the `~user` VM's own drain line appears. The ledger and `stadium_conserved()` hold (`fleet_conserved` is not evidence). No `native_stack_saved_sp` ever equals an address inside Hera's stack range (temporary probe). | | ~~**C**~~ | ~~§3.4 eligibility marked + pump skips participants (one commit).~~ Split into C-a/C-b by §3.13 (pending rulings C-1–C-4). | (see C-a, C-b) |
| **C-a** | §3.13.2 the running VM is charged at each of its own beats, with carry. Physics only; switching unchanged. | Temporary probe (reverted), which switches Hestia in with the Stage FR probe's `SB-GO`/`SB-BACK` words, because C-a itself switches nothing. Hestia runs a colon-word payload that produces ≥ 8 beats. Her `VM-HEAT` is sampled **at a beat during the turn** and again after it, and must rise from 0 by `beats × slope` (whole units, remainder kept in carry). The rest of the live fleet must fall by the same total. `vm_physics_conserved()` holds, and the `fleet_window` sample count is unchanged. **Baseline first:** the same probe on current code shows heat unchanged (proves G9). Then acceptance: `dict_hash` unchanged and the relay works. |
| **C-b** | §3.13.3 decision at owned checkpoints, beats, the trampoline and **the idle point** (§3.13.5, which absorbs Stage F's §3.7). Eligibility comes from the queue or a non-IDLE top frame. The pump skips participants. The ISR switch path and readiness are removed (§3.13.4). | The WIREBIND relay still works, now delivered by a real switch: `switch_count` increments across the relay and the `~user` VM's own drain line appears. Ledger and `stadium_conserved()` hold. `heartbeat_tick()` contains no switch call (source). **Walk-order probe** (temporary): every decision is also computed with the participant table walked in reverse; disagreements = 0. No `native_stack_saved_sp` ever inside Hera's stack range (temporary probe). No switch storm (log growth at idle `ok>`, QMP `send-key` computes a value). |
| **D** | §3.5 kill of parked-idle + unregister in `capsule_vm_kill()`. | Birth, message, idle-park, `KILL` succeeds; the switch-signal slot count returns to its prior value. | | **D** | §3.5 kill of parked-idle + unregister in `capsule_vm_kill()`. | Birth, message, idle-park, `KILL` succeeds; the switch-signal slot count returns to its prior value. |
| **E** | §3.6 generic registration in `capsule_birth_baby()`, duplicate call sites removed. | The Tripod and WIREBIND VMs still register exactly once (slot count), and a `BIRTH`-ed VM appears as a participant. | | **E** | §3.6 generic registration in `capsule_birth_baby()`, duplicate call sites removed. | The Tripod and WIREBIND VMs still register exactly once (slot count), and a `BIRTH`-ed VM appears as a participant. |
| **F** | §3.7 idle point is a switch point. | With Hera idle at `ok>`, a message sent to a worker **gets processed without typing anything**: the worker's output appears while the console is idle. | | **F** | §3.7 idle point is a switch point. *(If C-5 is ruled yes, the mechanism lands in C-b and F keeps only this evidence.)* | With Hera idle at `ok>`, a message sent to a worker **gets processed without typing anything**: the worker's output appears while the console is idle. |
| **G** | §3.8 `SEND`. | Two workers, each sent a payload that loops printing a tagged marker (`A` / `B`) N times: **the markers interleave** in the serial log. This is the one-line proof of concurrency, and it's the test this whole document exists to pass. | | **G** | §3.8 `SEND`. | Two workers, each sent a payload that loops printing a tagged marker (`A` / `B`) N times: **the markers interleave** in the serial log. This is the one-line proof of concurrency, and it's the test this whole document exists to pass. |
| **H** | §3.9 DoE driver rebuilt (capsule change only). | A single trial at cfg 4 (8 workers): `switch_current_slot` in the per-beat DoE rows moves across **multiple worker slots within one trial**, and `MU-TRIAL … fail=0`. Then Bob's call on relaunching the campaign. | | **H** | §3.9 DoE driver rebuilt (capsule change only). | A single trial at cfg 4 (8 workers): `switch_current_slot` in the per-beat DoE rows moves across **multiple worker slots within one trial**, and `MU-TRIAL … fail=0`. Then Bob's call on relaunching the campaign. |
| **I** | Strip dead code: the pump loop once every VM is a participant, `SWITCH-MARK-WORK` if nothing calls it, the global depth counter once attribution no longer needs it. | Per `FABRIC-3.5.md` §XXII's strip discipline: reachability shown three ways, not by grep alone. | | **I** | Strip dead code: the pump loop once every VM is a participant, `SWITCH-MARK-WORK` if nothing calls it, the global depth counter once attribution no longer needs it. | Per `FABRIC-3.5.md` §XXII's strip discipline: reachability shown three ways, not by grep alone. |
@@ -427,6 +721,22 @@ QMP `send-key` computing a value, per `FABRIC-3.6.md` task 1.5).
5. **§3.10's shared PRNG:** accept it for this pass (run order is still deterministic), or put 5. **§3.10's shared PRNG:** accept it for this pass (run order is still deterministic), or put
per-VM `RANDOM` state in scope. per-VM `RANDOM` state in scope.
6. **C-1 (§3.13.2):** how heat is charged under self-driven execution.
- Proposed (a): the running VM is charged at each of its own beats. Switch-out-only charging
is purely cooperative, unless decisions compare projected heat (a′).
- Also: charge records no `fleet_window` samples, which means the slope stays frozen at its
unvalidated seed under self-driven load. Or record them and let it adapt.
7. **C-2 (§3.13.2):** a per-VM carry on the new charge only. Without it, (a) moves nothing.
8. **C-3 (§3.13.3):** ties. The incumbent stays on a tie with itself. Among several candidates:
whether "waiting longest," by a single-writer arrival stamp, is acceptable under *"no scheduler
allowed,"* or whether Bob wants something else. Slot, UUID and registry order are rejected.
9. **C-4 (§3.13.2):** Hera's reservoir mass means the console gets no turn under worker load.
Options are (α) accept it, (β) compare charged heat instead of absolute heat, or (γ) something
else. No recommendation.
10. **C-5 (§3.13.5):** fold the idle point (Stage F's §3.7) into C-b, since the relay can't be
delivered by a switch without it. Also: Hera's pending console input counts as her mail for
eligibility.
**Nothing in this document authorizes an edit.** Per Captain Bob's Law, no stage starts without an **Nothing in this document authorizes an edit.** Per Captain Bob's Law, no stage starts without an
explicit instruction. explicit instruction.