# Artemis Block Heat Physics — Design Doc **Date:** 2026-07-08 (rev 2026-07-08a) **Branch:** `lithosananke` **Status:** Design only. Not implemented. Sibling doc to `HERMES-MESSAGE-CHANNEL-PHYSICS-DESIGN-20260708.md` and to `VM-PHYSICS-DYNAMIC-FLEET-DESIGN-20260705.md`'s "Fold-in question" section, which settled that messages and blocks get their own independent decay mechanisms — not merged into `VMPhysics`, not derived from its slope — each fit from its own rolling window, in separate follow-on docs. This is that doc for Artemis (block heat). See `HERMES-MESSAGE-CHANNEL-PHYSICS-DESIGN-20260708.md` for the message/channel sibling and `HERMES-MESSAGE-BLOCK-STORAGE-DESIGN-20260708.md` for the storage-layer doc this one's precedent comes from (Artemis's allocator shape). **Author:** Captain Bob / Claude Code **Correction (2026-07-09):** this doc's original Goal section (rev a) claimed block heat has no conservation invariant, citing a "settled fold-in decision" from `VM-PHYSICS-DYNAMIC-FLEET-DESIGN-20260705.md`. That claim was wrong — it was derived without first reading `.claude/ARTEMIS.md`, which is authoritative and states plainly: *"Every block Artemis manages participates in K≡1.0... Artemis's thermal contribution to the fleet is the sum of her active block heat"* and, more specifically, *"Every logical block's heat contributes to Artemis's K total. Reap must credit K back. Alloc must charge K correctly."* A full Tripod-docs audit (`TRIPOD.md`/`HERMES.md`/`ARTEMIS.md`) confirmed this directly contradicts what was below. The Goal section is corrected in place. The rest of this doc's mechanism — an independently-fit decay *rate* for blocks, its own `ArtemisHeatWindow` struct, separate from word-level, VM-fleet, and Hermes physics — is unaffected: `ARTEMIS.md` requires block heat to *participate in* K≡1.0 as a design target, not that this doc's narrower decay-rate question also finish the K-FLEET wiring gap `ARTEMIS-BAM-ACCEPTANCE-20260703.md` already tracks separately. --- ## Problem `capsules/artemis/init.4th` hardcodes `65208 CONSTANT Q-DECAY` (Block 4110) and applies it via straight multiplicative shrink in `ART-COOL` (Block 4139): for every allocated block in the `ART-DATA-BLKS` (22,998-entry) `BLK-HEAT` array with heat above zero, `heat = heat * Q-DECAY`. This is the same "before" picture already diagnosed twice this session: `65208` was independently re-declared as an identical hand-picked constant in three separate files (`compudynamics.4th`, `hermes/init.4th`, `artemis/init.4th`) — copy-pasted, uncoordinated, no shared derivation. VM heat's instance was fixed (`capsule_vm_physics.c`). Hermes's instance has a design doc (`HERMES-MESSAGE-CHANNEL-PHYSICS-DESIGN-20260708.md`), not yet implemented. This doc is Artemis's. `ARTEMIS-BAM-ACCEPTANCE-20260703.md` (Phase 1 acceptance) already flags the adjacent, larger gap this doc does not attempt to close: `ART-K-TOTAL` sums all block heat but was deliberately kept **observability only** — not wired into fleet-wide `K-FLEET`/`VM-CONSERVED?` — because "the correct fix (Logical BAM) requires normalizing per-block heat so that the allocated pool represents exactly `Q.1` of Artemis's share. That is deferred." This doc is scoped to the decay-rate question only (mirroring Hermes's doc exactly); the Logical BAM gap remains exactly as deferred as Phase 1 left it — see Explicitly out of scope. ## Goal Replace `Q-DECAY` with a `blk_decay_slope_q48` inferred from observed block cooling/reap behavior, mirroring the shape both the word-level and VM-fleet physics engines already use (rolling window, inferred rate, skip-rather-than-substitute-a-default when unwarmed), applied to a fourth kind of entity, with its own independent window and its own independent slope — not shared with Hermes's `msg_decay_slope_q48`/ `ch_decay_slope_q48`, not merged into VM-fleet's conservation semantics. **Block heat DOES participate in K≡1.0** — per `.claude/ARTEMIS.md`'s Compudynamic Invariant: *"Every block Artemis manages participates in K≡1.0. Block fetch, persist, and reap operations must rebalance K correctly."* Blocks are born hot (`Q.1`, `BLK-ALLOC`) and decay until reaped (`BLK-HEAT@ 0 =` in `ART-REAP`), same as messages, same as words — but unlike the earlier (wrong) framing of this doc, that decay is not conservation-exempt; it's the live thermal contribution `ARTEMIS.md` says must sum into Artemis's fleet share. This doc does not itself design the K crediting mechanism — `ARTEMIS.md`'s Future Material already specifies the target (`Alloc must charge K correctly... Reap must credit K back... The Physical BAM carries no K — only live logical blocks do`). What's still real and still out of scope here: **wiring** that target into `K-FLEET` today is `ARTEMIS-BAM-ACCEPTANCE-20260703.md`'s already-tracked, already-deferred gap (`ART-K-TOTAL` sums block heat but isn't wired into `K-FLEET` yet) — this doc's job is only the decay *rate*, on top of whatever K-crediting mechanism that other gap eventually completes, not a redesign of it. ## Why this can't just copy either existing mechanism verbatim The reasoning is structurally identical to Hermes's doc — worth stating directly rather than pointing elsewhere, since Artemis's version of it has its own specific shape: **Not word-level's shape:** `BLK-HEAT` entries are keyed by array index (`LBN>IDX`, offset into `ART-DATA-BLKS`), and a freed block's slot is immediately eligible for `BLK-ALLOC` to hand back out to an unrelated future allocation (`BLK-FREE` zeroes heat and clears the free-map bit in the same breath — Block 4113). Exactly like a message's arena slot, a block's identity does not persist across a free/realloc cycle, so per-entity trajectory replay (word-level's shape) doesn't apply. **Structurally the closest analog is still VM-fleet's rate-recovery shape, for the same reason as Hermes's doc:** the decay law itself (`heat *= Q-DECAY`) is fully known — nothing to curve-fit. What's actually worth inferring is whether the *rate* is well-matched to observed block churn (allocation/reap frequency), which is a different question than either existing engine answers. **One genuine difference from Hermes worth naming:** Artemis's `ART-TICK` (`ART-COOL ART-REAP`, Block 4140) is explicitly **not** wired to the automatic heartbeat yet — `ARTEMIS-BAM-ACCEPTANCE-20260703.md`'s Deferred/Next table lists "ART-TICK heartbeat" as its own separate open item, distinct from the Logical BAM gap. Today, `ART-COOL`/`ART-REAP` only run when something explicitly calls `ART-TICK` (the self-test words in Block 4141 do this manually). An inferred `blk_decay_slope_q48` is only as meaningful as the sampling cadence behind it — if `ART-TICK` isn't running on a real cadence, there's no real "observed churn" to infer against. **This doc's mechanism therefore has a harder precondition than Hermes's**: the heartbeat-wiring gap needs closing (or at minimum, a documented decision to sample only on manual `ART-TICK` calls, accepting that as the real cadence for now) before rate inference means anything. Flagged here, not resolved — see Open questions. ## The mechanism ### 1. What "inferring the rate" means here Same framing as Hermes's doc, applied to blocks: not re-deriving the known multiplicative law, but tracking **observed reap throughput relative to allocated-block occupancy** across `ART-TICK` calls. High churn (many `BLK-ALLOC`/`BLK-FREE` cycles between ticks) with a too-slow decay rate lets stale-but-still-hot blocks linger, holding free-map bits that new allocations need. Low churn with too-aggressive decay discards heat signal before it's useful for whatever eventually consumes it (today: nothing does — `ART-K-TOTAL` is observability only, per Problem above; this is preparing the mechanism for when something does). `blk_decay_slope_q48` should be a function of observed reap throughput relative to `ART-DATA-BLKS` occupancy at sample time, fit periodically from a rolling window of samples — the same shape as Hermes's `(live_count, reaped_since_last)` sample pair, applied to blocks instead of messages. ### 2. `ArtemisHeatWindow` — new struct, one instance (blocks only) ```c typedef struct { uint32_t live_count; /* allocated blocks at this sample */ uint32_t reaped_since_last; /* blocks freed since the prior sample */ } ArtemisHeatSample; typedef struct { ArtemisHeatSample samples[ARTEMIS_HEAT_WINDOW_DEPTH]; uint32_t head; uint32_t count; /* saturates at ARTEMIS_HEAT_WINDOW_DEPTH */ int is_warm; /* count >= ARTEMIS_HEAT_WINDOW_DEPTH */ } ArtemisHeatWindow; ``` One instance, not two — Artemis has one entity kind (blocks), unlike Hermes's messages/channels split. No structural reason to over-split this the way Hermes's doc deliberately avoided under-splitting. **`ARTEMIS_HEAT_WINDOW_DEPTH`: proposed 64, not yet settled.** Unlike Hermes's arena (`MSG-MAX`=32, about to grow), Artemis's scale is already large and stable — `ART-DATA-BLKS`=22,998 — so there's no equivalent "provisional against an about-to-change scale" caveat here. 64 is proposed only because it matches `VM_FLEET_WINDOW_DEPTH` and gives a similarly-sized statistical sample; it is still a starting guess, not derived from observed churn data, and should be treated the same way Hermes's window depth is: provisional until a real implementation pass with instrumented logs exists. ### 3. Recording — passive, hooked at the existing `ART-COOL` call site `ART-COOL` already sweeps every block with heat above zero once per call (Block 4139); a new `artemis_heat_sample(ArtemisHeatWindow*, uint32_t live_count, uint32_t reaped_since_last)` call at the end of `ART-COOL` (or at the end of `ART-TICK`, after `ART-REAP` has run and `reaped_since_last` is known — the more sensible placement, since `ART-COOL` alone doesn't know what `ART-REAP` is about to free) is the only new call site needed. **This is where the heartbeat-wiring precondition above bites concretely:** if `ART-TICK` only fires on manual self-test calls, samples only accumulate on those manual calls too. The window will still function correctly (it doesn't assume real-time spacing between samples, same as Hermes's), but "converges to reflect observed churn" only means anything once `ART-TICK` runs on a cadence resembling real usage. ### 4. Inference — heartbeat-gated, same skip-don't-substitute discipline `artemis_heat_tick()`, intended to be called from wherever `ART-TICK` ends up wired into the real heartbeat (dependent on that gap closing — see Open questions): when the window is warm, re-fit `blk_decay_slope_q48`; when unwarmed, leave the current slope untouched. Same philosophy as both existing engines and Hermes's doc. The concrete fit function is left open for the same reason Hermes's doc leaves it open: writing a specific formula without real observed-churn data first would be inventing content, not designing it. A first candidate worth prototyping, matching Hermes's doc's proposed starting hypothesis for consistency: target reap-latency-in-ticks, adjust slope proportionally to observed-vs-target ratio. ### 5. Application — replaces `Q-DECAY Q.*` at the one cool-sweep site `ART-COOL`'s `Q-DECAY Q.*` becomes `blk_decay_slope_q48 Q.*`. No change to the multiplicative decay shape itself, only to where the rate comes from. `ART-REAP` and `BLK-ALLOC`/`BLK-FREE`/`BLK-FETCH` are untouched — same "don't modify the hard-locked allocator primitives" discipline `HERMES-MESSAGE-BLOCK-STORAGE-DESIGN-20260708.md` states explicitly for `FM-*`/`BLK-ALLOC`/`BLK-FREE` (Blocks 4110–4113, ★ HARD LOCKED per `capsules/MANIFEST.md`) — this doc only touches `ART-COOL` (Block 4139), which is not in that locked set. ## Consumer code migration - **`artemis/init.4th` Block 4110**: `65208 CONSTANT Q-DECAY` deleted. - **`artemis/init.4th` Block 4139** (`ART-COOL`): `Q-DECAY` reference replaced with a call to fetch the current `blk_decay_slope_q48` (new C primitive, e.g. `BLK-DECAY-SLOPE@`); gains the sample-recording call described in section 3 (placement — inside `ART-COOL` or `ART-TICK` — left as an implementation decision, see section 3). - New kernel-only file pair, matching `capsule_vm_physics.c`'s and Hermes's planned `hermes_heat_physics.c`'s scope (Artemis-specific state, no hosted-build equivalent): - `src/starkernel/capsule/artemis_heat_physics.c` - `include/starkernel/artemis_heat_physics.h` - New FORTH-visible primitive: `BLK-DECAY-SLOPE@`, plus a status word mirroring `VM-PHYSICS-STATUS`'s and Hermes's planned status word's shape for diagnostics. ## What gets deleted - `artemis/init.4th` Block 4110: `65208 CONSTANT Q-DECAY`. Nothing else — `ART-COOL`/`ART-REAP`/`ART-TICK`/`BLK-ALLOC`/`BLK-FREE`/ `BLK-FETCH` keep their existing structure, only the decay-rate source at the one `ART-COOL` call site changes. ## Verification approach - Three-arch acceptance as usual (kernel-only code, `#ifdef __STARKERNEL__` gates per `.claude/CLAUDE.md`). - A targeted test: drive real block churn (repeated `BLK-ALLOC`/ `BLK-FREE` cycles via `ART-SELF-TEST`-style traffic, Block 4141) at two different rates across separate boot runs, confirm `blk_decay_slope_q48` converges to different values — the same class of test Hermes's doc and VM-fleet's design both use to confirm the inferred rate actually responds to real observed activity rather than sitting at its seed value. - Confirm `ArtemisHeatWindow`'s dead-entity handling (a block freed mid-window, its heat zeroed by `BLK-FREE` before the next sample) doesn't fault or corrupt the sample count — same class of test as VM-fleet's "kill-during-warm-up" test and Hermes's planned equivalent. - **Specific to this doc's precondition**: before trusting any convergence result, confirm `ART-TICK` is actually firing at a real, known cadence during the test run (manual self-test calls, or the heartbeat wiring if that gap is closed first) — a window fed by an unpredictable or absent cadence will produce numbers that look like convergence but aren't meaningful. ## Explicitly out of scope - **Logical BAM / `K-FLEET` wiring.** Already deferred in `ARTEMIS-BAM-ACCEPTANCE-20260703.md`'s own Phase 1 acceptance: "`ART-K-TOTAL` sums all block heats but is not wired into `K-FLEET`... The correct fix (Logical BAM) requires normalizing per-block heat... That is deferred." This doc gives block heat a decay *rate*, not the K-crediting wiring itself — those are separate problems. Per `ARTEMIS.md`, block heat IS meant to be conservation-eligible (see Goal, corrected); finishing that wiring remains Artemis's own gap to close on its own schedule, independently of Hermes's parallel storage work, and is not part of this decay-rate design. - **`ART-TICK` heartbeat wiring.** Named as a precondition above, but closing it is a separate, already-identified deferred item (`ARTEMIS-BAM-ACCEPTANCE-20260703.md`'s Deferred/Next table), not part of this design. - **Hermes message/channel heat** — sibling doc, `HERMES-MESSAGE-CHANNEL-PHYSICS-DESIGN-20260708.md`. No shared state, window, or slope between the two; each VM's heat physics is entirely its own, matching the zero-cross-VM-coupling architecture confirmed in `HERMES-MESSAGE-BLOCK-STORAGE-DESIGN-20260708.md`. - **The riscv64 heartbeat/compile race** noted in `ARTEMIS-BAM-ACCEPTANCE-20260703.md` as an unfixed, reported finding ("awaiting Captain Bob's direction"). Unrelated to block heat decay; not addressed here. - **The multi-level DoE rewrite** (word/VM-fleet/message/block as four independent metric spaces) — this doc gives block heat its decay mechanism; observing it as part of a real experiment is separate, larger work already on the punch list. - **Implementation** — this is a design doc. Writing `artemis_heat_physics.c` is follow-on work, not part of this pass. ## Open questions (explicitly not resolved — flagged, not guessed at) 1. **The `ART-TICK` heartbeat-wiring precondition.** This doc's mechanism only produces meaningful results once `ART-TICK` runs on a real cadence, not just manual self-test calls. Whether to close that gap first, or accept manual-call-driven sampling as the real cadence for an initial implementation pass, is unresolved. 2. **`ARTEMIS_HEAT_WINDOW_DEPTH` value.** Proposed 64, not derived from real observed churn data — needs a real implementation pass with instrumented boot logs before treating this as settled. 3. **The concrete fit function** for turning `(live_count, reaped_since_last)` samples into `blk_decay_slope_q48`. Section 4 deliberately stops short of a formula, same as Hermes's doc. 4. **`reaped_since_last` bookkeeping mechanism** — `ART-REAP` isn't currently instrumented to report a freed-count to whatever records the sample; same open plumbing question Hermes's doc flags for `MSG-REAP`. 5. **Sample placement** — inside `ART-COOL` (before `ART-REAP` runs, so `reaped_since_last` reflects the *previous* tick's reaps) vs. inside `ART-TICK` after both `ART-COOL` and `ART-REAP` have run (so the sample reflects this tick's reaps exactly). Section 3 leans toward the latter but doesn't commit.