321 lines
17 KiB
Markdown
321 lines
17 KiB
Markdown
# 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.
|