Add FABRIC.md — Stadium design, sections 1-18
Captures the 2026-08-03 design session: collapsing the four independent
heat/TTL/pin implementations (blocks, messages, console cells, ACLs) into
one bounded Stadium of fixed-size entries, driven by an engine below every VM.
Sections 1-15 are the original design argument. Sections 16-18 add:
- 16 Substrate findings. No IRQ return path exists on aarch64 or riscv64;
riscv64's time base is a hardcoded 1 GHz guess; the dictionary already
carries six of the seven entry wires; the engine must stay deterministic.
- 17 Patrons. TTL, heat decay and pin are three distinct mechanisms on one
tick, not a type field. Reap means leaves the floor, not destroyed. The
framebuffer is a utility, not a patron. Dynamic in capacity, static in
structure.
- 18 The engine (L0). L0 and L8 bookend the gated loops L1-L7, both ungated.
Jacquard stays 7-bit/128 states, accounting for L0 by its absence.
Dispatch enumerates behaviours, never patron kinds.
Determinism traced end to end and confirmed intact: TIME-TRUST is measured
and never fed back, inference inputs are wholly execution-derived, decay is
tick-based, and the parity hash covers only word name and execution_heat.
One pre-existing exception recorded — vm_physics_touch scales fleet heat by
wall-clock elapsed time, outside the parity path.
Draft. Sections marked DECIDED / LEANING / OPEN throughout.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
8ad9ac52aa
commit
8af19a545b
@@ -0,0 +1,925 @@
|
|||||||
|
# FABRIC.md — DRAFT
|
||||||
|
|
||||||
|
**Status:** Draft for review. Captures the design session of 3 August 2026.
|
||||||
|
**Nothing here is committed.** Sections are marked **DECIDED**, **LEANING**, or **OPEN** so
|
||||||
|
you can argue with it rather than inherit it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. The claim
|
||||||
|
|
||||||
|
StarshipOS currently has four subsystems that each independently implement the same
|
||||||
|
physics: Artemis heats blocks, Hermes ages messages, Console heats dirty cells, ACLs
|
||||||
|
carry heat and TTL. Four implementations, one pattern.
|
||||||
|
|
||||||
|
The claim is that this is one mechanism wearing four costumes, and that the dictionary
|
||||||
|
is already the reference implementation of it. Lift the dictionary one level of
|
||||||
|
abstraction and every subsystem becomes an instance rather than a special case.
|
||||||
|
|
||||||
|
The argument that decides it: **they already have the same wires.** Blocks felt
|
||||||
|
different because they are large and live on disk — but size and location are not
|
||||||
|
properties, they are payload details. Strip those away and a block has exactly what a
|
||||||
|
message has.
|
||||||
|
|
||||||
|
**DECIDED.** Direction is not optional. The remaining question is effort, not validity.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. The arena
|
||||||
|
|
||||||
|
A single region of memory, outside any VM, holding everything currently **live**.
|
||||||
|
|
||||||
|
- Bounded capacity. The bound is real and inescapable, and it is what gives K≡1.0 a
|
||||||
|
fixed denominator. Without a hard outer wall, K is bookkeeping rather than a
|
||||||
|
conservation law.
|
||||||
|
- Allocated at boot, before any VM exists.
|
||||||
|
- Not part of the heap.
|
||||||
|
|
||||||
|
The critical scoping decision, and the one that keeps this from sprawling:
|
||||||
|
|
||||||
|
> **The arena holds what is live. Not everything that exists.**
|
||||||
|
|
||||||
|
**DECIDED.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. The entry
|
||||||
|
|
||||||
|
One structure. No variants, no type field, no subclassing.
|
||||||
|
|
||||||
|
| Wire | Meaning |
|
||||||
|
|---|---|
|
||||||
|
| identity | handle or name |
|
||||||
|
| heat | current thermal state |
|
||||||
|
| TTL | remaining lifetime |
|
||||||
|
| pin | invariance flag (opposite of TTL, not an extension of it) |
|
||||||
|
| link | index into the arena, not a pointer |
|
||||||
|
| code field | what to do when this entry is worked |
|
||||||
|
| payload | inline if small, by reference if large |
|
||||||
|
|
||||||
|
Fixed-size cells. Links are indices, so the arena stays an array — no fragmentation,
|
||||||
|
and tractable for Isabelle later.
|
||||||
|
|
||||||
|
**The code field is the entire type system.** A block's code field migrates. A message's
|
||||||
|
delivers. A VM's ticks. The engine never asks what kind of thing it is holding; it
|
||||||
|
heats, ranks, reaps, and calls the code field.
|
||||||
|
|
||||||
|
> If you find yourself wanting a type field so the engine can branch on entry kind, the
|
||||||
|
> design has gone wrong. The code field already answers that question.
|
||||||
|
|
||||||
|
**DECIDED**, except payload threshold — see Open Questions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Heat
|
||||||
|
|
||||||
|
Heat is **conferred by traffic, not intrinsic to the entry.**
|
||||||
|
|
||||||
|
This is the piece that was missing for most of the session. Nothing decides what matters.
|
||||||
|
An entry is hot because activity is concentrated around it — the way a crowd in front of
|
||||||
|
one car makes that corner of the hall hot. Density generates heat; nobody computes it.
|
||||||
|
|
||||||
|
Consequences:
|
||||||
|
|
||||||
|
- **Ranking is read, not decided.** There is no scheduler because there is no policy.
|
||||||
|
The arena is simply already in heat order when you look at it.
|
||||||
|
- **K constrains the total,** so ordering is forced by conservation rather than by tuned
|
||||||
|
parameters. There is nothing to tune wrongly. This is the defensible distinction from
|
||||||
|
a scheduler and it belongs in the write-up.
|
||||||
|
- **Popularity is self-limiting.** A crowded entry is harder to reach, which throttles
|
||||||
|
traffic to it, which cools it. The governor is local and emergent — no global damping
|
||||||
|
constant to pick.
|
||||||
|
|
||||||
|
TTL expiry stays unconditional: entries leave at their own time, unscheduled, nobody's
|
||||||
|
decision. Pinning remains the separate, opposite mechanism — invariance, not longevity.
|
||||||
|
|
||||||
|
**LEANING.** The causality is right; the density formulation needs a concrete definition.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. What is *not* in the arena
|
||||||
|
|
||||||
|
This section exists because forcing everything in is how this design turns into a mess.
|
||||||
|
|
||||||
|
- **Storage is beneath the arena.** The show floor is not the warehouse. Artemis is where
|
||||||
|
entries live when they are not in play. Blocks migrate onto the floor when hot and back
|
||||||
|
out when cold — which is heat-driven block migration, already built. Artemis does not
|
||||||
|
become an arena occupant; it becomes what the arena pages against.
|
||||||
|
- **Devices are beside the arena.** The framebuffer is the building's lighting, not an
|
||||||
|
occupant. Console's dirty *events* are arena entries; the pixels are not.
|
||||||
|
|
||||||
|
**DECIDED.** Three sharp edges, nothing forced.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Boot order
|
||||||
|
|
||||||
|
The engine cannot be a VM service, because VMs live inside the thing it manages.
|
||||||
|
|
||||||
|
1. LithosAnanke establishes the arena and starts the engine.
|
||||||
|
2. Hera becomes the first entry in it.
|
||||||
|
3. Hera births everything else, sizing each VM as it goes.
|
||||||
|
|
||||||
|
Structurally the same move as minting Zuse's certificate at first boot: a root that
|
||||||
|
cannot be produced by the mechanism it grounds.
|
||||||
|
|
||||||
|
**LEANING.** Order is right; the allocation mechanism is unspecified.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Hera
|
||||||
|
|
||||||
|
Hera's job becomes arena distribution. This is not a new responsibility — allocating a
|
||||||
|
VM's share *is* birthing it, and lifecycle is already what Hera is for.
|
||||||
|
|
||||||
|
**OPEN:** whether a VM's share is a hard bound or an elastic one that can grow and shrink
|
||||||
|
under pressure, with capacity transferring between VMs as a conserved operation Hera
|
||||||
|
arbitrates. Elastic is more powerful and more work. Under elasticity, birth sizes the
|
||||||
|
*rest* volume rather than a cap — a more forgiving thing to have to guess right.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. The mental model
|
||||||
|
|
||||||
|
An auto show hall.
|
||||||
|
|
||||||
|
Cars and people, in a building with a fixed capacity. People arrive and leave at their
|
||||||
|
own times. They ask questions and converse — those are the messages. They stand in front
|
||||||
|
of a car for a while and move on. Occasionally one sits in a car, which is the only
|
||||||
|
exclusive thing in the room, scoped to a single object, no global lock.
|
||||||
|
|
||||||
|
The hall gets crowded. Crowds get hot.
|
||||||
|
|
||||||
|
**One discipline to hold:** cars and people cannot be two structures. That would be a type
|
||||||
|
field re-entering through a metaphor. They are one entry shape differing only in TTL and
|
||||||
|
code field — a car's lifetime is the show, a person's is a visit; a car's code field is
|
||||||
|
*be attended to*, a person's is *move and attend*.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. The admission test
|
||||||
|
|
||||||
|
Before writing code, run this on paper against every candidate entry type. Two questions,
|
||||||
|
both of which must have a non-forced answer:
|
||||||
|
|
||||||
|
1. **What does heat mean for this thing?**
|
||||||
|
2. **What is its reap event?**
|
||||||
|
|
||||||
|
| Type | Heat means | Reap is | Verdict |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Block | accessed often | migration | passes |
|
||||||
|
| Message | delivery urgency | delivery | passes |
|
||||||
|
| VM | runs often | execution / death by cooling | passes |
|
||||||
|
| ACL | checked often | ? | check |
|
||||||
|
| Screen cell | ? | redraw, which removes nothing | **suspect** |
|
||||||
|
|
||||||
|
Screen cells are the one to resolve first. A cell never expires — it is a fixed grid
|
||||||
|
position always present. If cells are permanent arena entries, most of the arena is inert
|
||||||
|
and permanently pinned. The likely correct read is that the arena entry is the **dirty
|
||||||
|
event**, not the cell: transient, honest TTL, and the grid stays outside where it belongs.
|
||||||
|
|
||||||
|
Ten minutes on paper. Either it confirms the design or it finds the one case that breaks
|
||||||
|
it, before any code moves.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Sequencing
|
||||||
|
|
||||||
|
**FABRIC.md first, then Hermes native on the fabric, then measure, then Console, then
|
||||||
|
Artemis last.**
|
||||||
|
|
||||||
|
Reasoning:
|
||||||
|
|
||||||
|
- Hermes is unfinished, which is lucky. Finishing it the old way and refactoring later
|
||||||
|
means deliberately writing code already slated for deletion. Build it on the fabric
|
||||||
|
directly and it carries zero migration debt.
|
||||||
|
- It becomes the proving ground — the fabric gets tested against a real subsystem before
|
||||||
|
anything that currently works is touched.
|
||||||
|
- **It produces the effort number empirically.** What Hermes costs is the multiplier for
|
||||||
|
everything else. One data point from real work beats any amount of estimating.
|
||||||
|
- Artemis reads, writes, and persists reliably today. That is banked. It goes last,
|
||||||
|
because it is the thing you cannot afford to break.
|
||||||
|
|
||||||
|
Existing instrument: the POST suite exercises every dictionary word and was already
|
||||||
|
earmarked as the regression gate for the shrink-to-colon-definitions pass. Same tool,
|
||||||
|
second job.
|
||||||
|
|
||||||
|
**Caution:** a green POST suite does not mean K still holds. Those are different claims.
|
||||||
|
The DoE campaign validated K on the *current* substrate; changing the substrate means
|
||||||
|
re-running it. Automated, but budget for it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Where the debt accrues
|
||||||
|
|
||||||
|
- **Dual paths — avoidable, and the big one.** Never two live heat mechanisms at once.
|
||||||
|
Convert one subsystem completely, prove it, move on. Every shim bridging old and new is
|
||||||
|
debt, and new code will get written against whichever is convenient.
|
||||||
|
- **Speculative generality — avoidable.** Only add a wire when a second entry type needs
|
||||||
|
it. Generality that never pays back is still debt.
|
||||||
|
- **The exception — not avoidable, so decide it early.** If one subsystem does not fit and
|
||||||
|
gets special-cased, that special case is permanent and worse than not unifying: you
|
||||||
|
carry the general machinery *and* the exception, and every future reader learns both.
|
||||||
|
This is why the admission test comes before code.
|
||||||
|
|
||||||
|
**Early signal:** ARTEMIS.md, HERMES.md, CONSOLE.md and TRIPOD.md each currently describe
|
||||||
|
their own heat mechanics. After FABRIC.md, each should shrink to roughly three lines —
|
||||||
|
what an entry is here, what heat means, what the reap event is. If any one of them gets
|
||||||
|
*longer*, that subsystem is fighting the fabric, and you will know which one before
|
||||||
|
writing code.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Open questions
|
||||||
|
|
||||||
|
1. Payload threshold — what size goes inline versus by reference.
|
||||||
|
2. Arena entry header size. Cardinality spans orders of magnitude (dozens of VMs,
|
||||||
|
thousands of messages, potentially very many screen events). The header must be sized
|
||||||
|
for the worst case, and that case is the screen. Sizing this constrains everything
|
||||||
|
else, so settle it early.
|
||||||
|
3. Screen cells: entry-per-cell or entry-per-dirty-event. (Leaning: event.)
|
||||||
|
4. Per-VM share — hard bound or elastic under pressure.
|
||||||
|
5. Loop coupling. Roughly eight feedback loops once Hera and heartbeat depth are counted.
|
||||||
|
The algorithms are known; the risk is interference. Usual discipline is separation of
|
||||||
|
timescales — keep nested loop periods an order of magnitude apart. Cheaper to decide
|
||||||
|
than to debug.
|
||||||
|
6. Whether the arena is one region for the whole system or nested per VM. Nested implies
|
||||||
|
K conserved at each level with messages as the only thing crossing a boundary, which
|
||||||
|
would mean no shared-memory atomicity is ever needed. Single region is simpler but
|
||||||
|
reintroduces locking — the one mechanism this architecture has otherwise never wanted.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. What this does to formal verification
|
||||||
|
|
||||||
|
This may be the largest payoff, and it was not the reason for the change.
|
||||||
|
|
||||||
|
Verifying four subsystems means four state models, four conservation arguments, and — the
|
||||||
|
expensive part — proofs about how they interact. That last category grows combinatorially
|
||||||
|
and is where a verification effort usually dies. Unification deletes it outright.
|
||||||
|
|
||||||
|
What the design gives Isabelle/HOL, more or less for free:
|
||||||
|
|
||||||
|
- **One datatype.** The arena entry is a single record. Everything else is payload. You
|
||||||
|
reason about `entry` once rather than about blocks, messages, VMs and events separately.
|
||||||
|
- **No pointers.** Fixed-size cells with index links means the arena models as a total
|
||||||
|
function over a finite index set — no heap model, no separation logic, no aliasing, no
|
||||||
|
null. This is the single biggest difference between a tractable proof effort and a
|
||||||
|
research project.
|
||||||
|
- **Finite state.** Bounded capacity means the state space is finite. Induction over the
|
||||||
|
arena is straightforward, and model checking becomes available alongside theorem proving.
|
||||||
|
- **One conservation theorem.** *Every engine operation preserves K.* Proved once against
|
||||||
|
the engine, it holds for every entry kind — because the engine cannot distinguish them.
|
||||||
|
Previously this was four proofs plus their interactions.
|
||||||
|
- **A clean model boundary.** Storage below and devices beside the arena means disk I/O and
|
||||||
|
framebuffer writes sit outside the model, at the C primitive boundary already drawn.
|
||||||
|
- **A trivial initial state.** Boot order — kernel, then arena, then engine, then Hera —
|
||||||
|
gives a base case that is trivially conserving, with everything else following by
|
||||||
|
induction on operations.
|
||||||
|
|
||||||
|
**One constraint this imposes, and it is not optional.**
|
||||||
|
|
||||||
|
The code field is late-bound behaviour, which is the one part of this that HOL does not
|
||||||
|
like: an arbitrary function stored in a record is higher-order and can wreck termination
|
||||||
|
arguments. The fix is a design rule rather than a proof technique:
|
||||||
|
|
||||||
|
> **The set of code-field behaviours must be a closed enumeration, fixed at build time.**
|
||||||
|
|
||||||
|
Model it as a datatype of behaviour tags plus a dispatch function and the whole thing stays
|
||||||
|
first-order and tractable. Leave the code field open as a general extension point and you
|
||||||
|
have traded four easy verification problems for one genuinely hard one.
|
||||||
|
|
||||||
|
This is consistent with the existing rule that adding a primitive requires rebuilding from
|
||||||
|
source rather than doing it from inside a running system. Worth stating explicitly in the
|
||||||
|
fabric design, because it is the kind of constraint that gets casually violated later by
|
||||||
|
someone adding "just one" dynamic behaviour.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14. Formalism
|
||||||
|
|
||||||
|
The thermodynamic analogy holds in places and inverts in one, which matters for the paper
|
||||||
|
but not for the build.
|
||||||
|
|
||||||
|
- Fixed capacity → closed system. K≡1.0 → conservation. Capacity transfer → work. These
|
||||||
|
map cleanly.
|
||||||
|
- **Heat is not entropy.** Heat is closer to energy or temperature. Entropy would measure
|
||||||
|
how heat is *distributed*: concentrated is low, uniform is high.
|
||||||
|
- **This matters practically.** K is conserved, so K can never tell you anything — it is
|
||||||
|
1.0 by construction, a correctness check rather than a diagnostic. Entropy over the heat
|
||||||
|
distribution actually varies, and distinguishes idle from productive from thrashing.
|
||||||
|
That is the real instrument, and the quantity worth driving the LED matrix with.
|
||||||
|
- **The inversion:** the second law says entropy rises spontaneously. This system does the
|
||||||
|
opposite — it self-organizes, concentrating heat where work happens. That is not
|
||||||
|
equilibrium thermodynamics; it is a **driven dissipative system**, order sustained by
|
||||||
|
throughput. Prigogine, not Carnot. A stronger claim, but only if stated correctly —
|
||||||
|
writing "thermodynamic system" while entropy decreases unprompted is an easy shot for a
|
||||||
|
reviewer.
|
||||||
|
|
||||||
|
Phenomenon first, then mathematics. The formalism follows the phenomenon; it does not gate
|
||||||
|
the build, and it is not finished until it is correct.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 15. The whole thing in five lines
|
||||||
|
|
||||||
|
- The arena holds the live crowd. Storage is the warehouse. Devices are the building.
|
||||||
|
- One entry shape. The code field is the only difference between kinds.
|
||||||
|
- Heat is density, conferred by traffic. Nobody decides.
|
||||||
|
- Departure is unconditional. Pinning is invariance, not longevity.
|
||||||
|
- The kernel opens the hall. Hera walks in first.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 16. Substrate findings — 2026-08-03
|
||||||
|
|
||||||
|
Naming: the arena is now called the **Stadium**, because `src/starkernel/vm/arena.c`
|
||||||
|
already owns "arena" for the PMM-backed VM page allocator — an unrelated concept. Sections
|
||||||
|
1–15 above still say arena and have not been reconciled.
|
||||||
|
|
||||||
|
Four findings from reading the tree. The first three change what step one costs. The
|
||||||
|
fourth changes what the engine is allowed to be.
|
||||||
|
|
||||||
|
### 16.1 There is no interrupt return path on two of three ISAs
|
||||||
|
|
||||||
|
The engine has to be driven from outside the VMs (§6), which in a kernel means interrupts.
|
||||||
|
That mechanism does not currently exist on most of our targets.
|
||||||
|
|
||||||
|
- `apic_timer_start()` is an explicit no-op stub on aarch64 (`arch/aarch64/apic.c:82`) and
|
||||||
|
riscv64 (`arch/riscv64/apic.c:76`). Both say the driver is deferred.
|
||||||
|
- `heartbeat_tick()` is defined on all three architectures and *called* from exactly one
|
||||||
|
site in the tree: `arch/amd64/interrupts.c:337`. On the other two it is dead code.
|
||||||
|
- Worse: every vector in `arch/aarch64/isr.S` — IRQ included — is a bare branch to a
|
||||||
|
handler that prints and enters `for(;;) wfe`. `arch/riscv64/isr.S` is the same shape.
|
||||||
|
There is no register save, no `ERET`, no `SRET`.
|
||||||
|
|
||||||
|
So enabling a timer interrupt today halts the kernel on the first tick. The work is not
|
||||||
|
"write a timer driver," it is "build the interrupt return path that was never built."
|
||||||
|
|
||||||
|
**Consequence for §12 Q5.** That question assumes a hierarchy of loop periods kept an
|
||||||
|
order of magnitude apart. Separation of *timescales* presupposes a time base. There is
|
||||||
|
one real time source, on one architecture; everything else paces off execution count.
|
||||||
|
Q5 cannot be answered on the current substrate — it is downstream of this work, not
|
||||||
|
parallel to it.
|
||||||
|
|
||||||
|
### 16.2 riscv64's time base is a guess
|
||||||
|
|
||||||
|
`arch/riscv64/timer.c:46` sets `s_counter_hz = 1000000000ULL` with the comment
|
||||||
|
`/* assume 1 GHz */`. The file header concedes `rdcycle`'s frequency is not
|
||||||
|
architecturally discoverable.
|
||||||
|
|
||||||
|
Every heartbeat variance and TIME-TRUST figure riscv64 has produced was computed against
|
||||||
|
a wrong `expected_delta`. This has to be fixed as part of any timer work, and it means
|
||||||
|
riscv64 timing numbers before and after that fix are not comparable.
|
||||||
|
|
||||||
|
### 16.3 The dictionary is already a Stadium
|
||||||
|
|
||||||
|
§1 claims the dictionary is the reference implementation. It is stronger than that.
|
||||||
|
`DictEntry` today carries six of the seven wires in §3:
|
||||||
|
|
||||||
|
| §3 wire | Already in `DictEntry` |
|
||||||
|
|---|---|
|
||||||
|
| identity | name / `word_id` |
|
||||||
|
| heat | `physics.*` (Loop #1) |
|
||||||
|
| TTL | `acl_ttl` |
|
||||||
|
| pin | `acl_pinned`, plus `WORD_PINNED` / `WORD_FROZEN` |
|
||||||
|
| link | dictionary chaining |
|
||||||
|
| code field | literally a function pointer |
|
||||||
|
|
||||||
|
The dictionary is not *analogous* to a Stadium entry. It is one, already built and already
|
||||||
|
tested. Everything else is what gets generalized toward it.
|
||||||
|
|
||||||
|
**But run §9's admission test on it before moving it in.** Its reap event is the weak
|
||||||
|
wire. Blocks migrate, messages deliver, VMs die by cooling — a dictionary word does not
|
||||||
|
expire. Heat decays to a floor and the word stays; `FORGET` is manual and rare. That is
|
||||||
|
the same shape §9 already flags as **suspect** for screen cells: hundreds of permanently
|
||||||
|
resident, largely inert entries. It may well be fine, but the dictionary is too central
|
||||||
|
to wave through, and it is precisely the case §9 exists to catch.
|
||||||
|
|
||||||
|
**Also:** the dictionary is what `parity.c` hashes. Moving its representation into the
|
||||||
|
Stadium changes that hash, so every committed baseline in `logs/` shifts. Not a blocker —
|
||||||
|
but a deliberate re-baseline with a before/after record, not something to discover later.
|
||||||
|
|
||||||
|
### 16.4 The engine must stay deterministic — this is a new constraint
|
||||||
|
|
||||||
|
Nothing in §1–15 says this, and it binds the engine tightly.
|
||||||
|
|
||||||
|
`parity.c` logs a dictionary hash per VM birth. The DoE's 0.000% CV across 90 runs and the
|
||||||
|
patent support material both rest on the same capsule producing the same heat state on
|
||||||
|
every run. Today that holds for a reason worth naming: ticking is **execution-driven**.
|
||||||
|
`vm_tick()` (`vm/vm_runtime.c:114`) is called from execution paths, and its own header
|
||||||
|
says *"Synchronous (now): Called from main execution loop, every N executions."* Same
|
||||||
|
instruction sequence, same tick points, same decay events, same hash.
|
||||||
|
|
||||||
|
Wall-clock ticking does not have that property. Under TCG, elapsed time varies run to run
|
||||||
|
on identical input.
|
||||||
|
|
||||||
|
> **The interrupt may supply pacing, but the engine must fire on tick *count*, never on
|
||||||
|
> elapsed wall time.**
|
||||||
|
|
||||||
|
Same input → same tick ordinal → same reap and inference events → same hash. This keeps
|
||||||
|
parity intact while still letting compudynamics be genuinely timer-driven.
|
||||||
|
|
||||||
|
There is a second, narrower version of the same discipline. `heartbeat_tick()` measures
|
||||||
|
inter-tick deltas to derive variance and TIME-TRUST. If the engine's own work ran inside
|
||||||
|
that handler, the handler's runtime would become part of the interval it measures — the
|
||||||
|
instrument would be reporting the cost of running the instrument. So the interrupt does
|
||||||
|
bookkeeping only; the engine runs outside it. The split already exists in the tree and
|
||||||
|
works: `adaptive_check_accumulator` / `adaptive_pending` (`include/vm.h:113-114`), set at
|
||||||
|
`rolling_window_of_truth.c:372-375`, serviced at `:1302-1308`.
|
||||||
|
|
||||||
|
**DECIDED** unless argued — it is a constraint inherited from what the system already
|
||||||
|
claims, not a new preference.
|
||||||
|
|
||||||
|
### 16.5 What this implies about order
|
||||||
|
|
||||||
|
Whatever step one turns out to be, it now has a floor under it: real timer interrupts and
|
||||||
|
a real IRQ return path on all three ISAs. §10's sequencing (Hermes first, as the proving
|
||||||
|
ground) sits above that floor, not below it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 17. Patrons
|
||||||
|
|
||||||
|
An occupant of the Stadium is a **patron**. Blocks, words, ACLs and messages are all
|
||||||
|
patrons. The word is doing real work: it names the category without implying a class
|
||||||
|
hierarchy, and it keeps the metaphor honest — patrons attend, they are not the building.
|
||||||
|
|
||||||
|
**DECIDED.**
|
||||||
|
|
||||||
|
### 17.1 Four patrons die four different ways — and that is not a type field
|
||||||
|
|
||||||
|
The observation that prompted this section is correct: these things do not all end the
|
||||||
|
same way. A message is consumed. An ACL lapses. A block should never be destroyed. A word
|
||||||
|
should never be destroyed either.
|
||||||
|
|
||||||
|
The reflex is a decision branch on patron kind. That is the type field §3 forbids, and it
|
||||||
|
is not needed — but neither is the opposite over-simplification, which an earlier draft of
|
||||||
|
this section made and which is corrected here.
|
||||||
|
|
||||||
|
**Heat and TTL are not the same mechanism, and neither is a special case of the other.**
|
||||||
|
§3 lists them as separate wires and they must stay separate. A message carries a countdown.
|
||||||
|
A block does not — a block leaves the floor because it *cooled*, not because a timer
|
||||||
|
expired. Collapsing the two forces the design, which is precisely the failure §11 warns
|
||||||
|
about.
|
||||||
|
|
||||||
|
There are three mechanisms, and each patron uses the ones that genuinely apply:
|
||||||
|
|
||||||
|
| Mechanism | Nature | Patrons | Departure |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **TTL** | countdown to a definite event | messages, ACLs | expiry |
|
||||||
|
| **Heat decay** | continuous, gradual | blocks, words | cooling off the floor |
|
||||||
|
| **Pin** | invariance — §3's wire | any | never |
|
||||||
|
|
||||||
|
Mapped per patron:
|
||||||
|
|
||||||
|
| Patron | Governed by | Reap event |
|
||||||
|
|---|---|---|
|
||||||
|
| Message | TTL | delivery — consumed, gone |
|
||||||
|
| ACL | TTL | expiry |
|
||||||
|
| Block | heat decay | **migration back to Artemis** — evicted, not destroyed |
|
||||||
|
| Word | heat decay | cooling off the floor (see §17.3) |
|
||||||
|
|
||||||
|
#### Two measures, one clock
|
||||||
|
|
||||||
|
This does **not** mean two clocks. Both mechanisms advance off the same tick — the
|
||||||
|
adaptive heartbeat. TTL decrements on a tick; heat decays on a tick. They are two different
|
||||||
|
*readings* of one counter, not two independent time sources.
|
||||||
|
|
||||||
|
That is not a tidiness preference, it is forced. §16.4 requires the engine to fire on tick
|
||||||
|
count so that the same input reproduces the same dictionary hash. Two independent clocks
|
||||||
|
would be two independent sources of nondeterminism and parity would not survive it.
|
||||||
|
|
||||||
|
> **One tick. Two measures. Three mechanisms.**
|
||||||
|
|
||||||
|
The engine still asks nothing about patron kind. It advances the tick, applies whichever
|
||||||
|
measures a patron carries, and calls the code field when a patron departs. A pinned patron
|
||||||
|
never departs. There is no type interrogation — see §18 for how the dispatch works without
|
||||||
|
one.
|
||||||
|
|
||||||
|
### 17.2 Reaping is not destruction
|
||||||
|
|
||||||
|
The block case is the one that makes this work, and §9 already had it right: a block's
|
||||||
|
reap event **is migration**. §5 puts storage beneath the Stadium, with blocks coming onto
|
||||||
|
the floor when hot and going back off when cold.
|
||||||
|
|
||||||
|
So a block is reaped in exactly the sense the engine means — it leaves the floor. Where it
|
||||||
|
goes afterwards is the code field's business, not the engine's. A message's code field
|
||||||
|
ends in delivery; a block's ends in a write-back to Artemis. Same event, different
|
||||||
|
behaviour, no special case.
|
||||||
|
|
||||||
|
This is worth stating plainly because "reap" reads as "free" and here it does not:
|
||||||
|
|
||||||
|
> **Reap means leaves the floor. It does not mean destroyed.**
|
||||||
|
|
||||||
|
**DECIDED.**
|
||||||
|
|
||||||
|
### 17.3 Words: the dictionary is the warehouse, hot words are the patrons
|
||||||
|
|
||||||
|
§16.3 left words as the unresolved patron. Pinning all of them resolves nothing — several
|
||||||
|
hundred permanently resident, largely inert entries is the §9 screen-cell failure with a
|
||||||
|
different label, and it wastes the bounded capacity that gives K a fixed denominator.
|
||||||
|
|
||||||
|
The better reading applies §5 unchanged. Storage sits beneath the Stadium. The **full
|
||||||
|
dictionary sits beneath it too**, and only **hot words are on the floor**.
|
||||||
|
|
||||||
|
This is not speculative — it already exists and is already measured:
|
||||||
|
|
||||||
|
- `src/physics_hotwords_cache.c` maintains the hot-word set
|
||||||
|
- `cache_hits_delta` is column 4 of the DoE CSV, "hot-words cache hits this tick"
|
||||||
|
- execution heat (Loop #1) is what promotes a word; linear decay (Loop #3) is what cools it
|
||||||
|
|
||||||
|
So the hot-word population is already a live, moving crowd with an existing promotion rule
|
||||||
|
and an existing cooling rule. It is the crowd. The dictionary is the warehouse it is drawn
|
||||||
|
from, exactly as Artemis is the warehouse blocks are drawn from.
|
||||||
|
|
||||||
|
#### The existing cache is only half-aligned — and that is the argument for doing this
|
||||||
|
|
||||||
|
Reading `physics_hotwords_cache.c` closely turns up something that strengthens the case
|
||||||
|
rather than weakening it. **Heat governs admission to the cache. Nothing governs
|
||||||
|
departure.**
|
||||||
|
|
||||||
|
`hotwords_cache_promote()` (`:362-383`), when full, writes the new word to
|
||||||
|
`cache[lru_index]` and advances that index modulo the size. That is round-robin. The field
|
||||||
|
is named `lru_index`, the inline comment at `:365` says "LRU eviction: remove oldest entry
|
||||||
|
(round-robin)", and the doc block at `:347` says "round-robin least-recently-used" — which
|
||||||
|
is a contradiction in terms. Nothing anywhere tracks recency of use. Promotion is gated on
|
||||||
|
`execution_heat > HOTWORDS_EXECUTION_HEAT_THRESHOLD` (`:283`); eviction consults heat not
|
||||||
|
at all.
|
||||||
|
|
||||||
|
The consequence is that the hottest word in the cache can be evicted purely because its
|
||||||
|
slot came up in the rotation.
|
||||||
|
|
||||||
|
That is a direct contradiction of §4:
|
||||||
|
|
||||||
|
> *Ranking is read, not decided. There is no scheduler because there is no policy. The
|
||||||
|
> arena is simply already in heat order when you look at it.*
|
||||||
|
|
||||||
|
Round-robin eviction is exactly a policy — an arbitrary one, uninformed by the physics the
|
||||||
|
rest of the system runs on.
|
||||||
|
|
||||||
|
**This is the strongest practical argument for §17.3.** Moving words onto the Stadium is
|
||||||
|
not a relabeling exercise; it repairs a real defect by deleting the arbitrary half of an
|
||||||
|
existing mechanism. And it is measurable before and after: `stats.evictions`,
|
||||||
|
`stats.promotions` and `stats.cache_hits` are already instrumented and already flow into
|
||||||
|
the DoE CSV.
|
||||||
|
|
||||||
|
Consequences if this holds:
|
||||||
|
|
||||||
|
- Words need no pin exception. Their reap event is cooling off the floor — the same shape
|
||||||
|
as a block's, one level up.
|
||||||
|
- §16.3's objection dissolves. The dictionary does not move into the Stadium wholesale;
|
||||||
|
it stays beneath it and pages against it.
|
||||||
|
- The parity concern in §16.3 narrows considerably. The dictionary's own representation is
|
||||||
|
not what changes — what becomes a patron is the hot set, which is already transient.
|
||||||
|
- Pin stops being a general-purpose escape hatch and goes back to meaning what §3 says:
|
||||||
|
invariance, for the few things that genuinely must not vary.
|
||||||
|
|
||||||
|
**LEANING.** The mechanism is already built and the fit is clean, but this reframes a
|
||||||
|
direction stated differently earlier the same day, and it deserves longer than a paragraph.
|
||||||
|
|
||||||
|
### 17.4 Open
|
||||||
|
|
||||||
|
1. **What is a word's TTL, concretely?** Heat decay already cools words, but decay-to-cold
|
||||||
|
and TTL-expiry are not obviously the same clock. Either they unify or §17.3 needs a
|
||||||
|
second mechanism, which would be a bad sign.
|
||||||
|
2. ~~**Is the hot-word set bounded today?**~~ **RESOLVED — yes, hard bounded.**
|
||||||
|
`DictEntry *cache[HOTWORDS_CACHE_SIZE]` (`include/physics_hotwords_cache.h:168`) is a
|
||||||
|
fixed array inside the struct, with `HOTWORDS_CACHE_SIZE = 32` (`:84`). Nothing is
|
||||||
|
allocated — `hotwords_cache_cleanup()` notes there is nothing to free, since the cache
|
||||||
|
holds borrowed pointers the dictionary owns. It is per-VM (`vm->hotwords_cache`, used
|
||||||
|
at `dictionary_management.c:320`), not global. This is exactly the inescapable outer
|
||||||
|
wall §2 requires.
|
||||||
|
|
||||||
|
Two things follow. **First, the bound is 32** out of a 453-word Mama dictionary — a
|
||||||
|
very tight floor. Whether that is the right Stadium population or an artifact of the
|
||||||
|
structure having been sized as a lookup cache rather than as a live set is a design
|
||||||
|
input, not a given. **Second**, the eviction defect in §17.3 above.
|
||||||
|
|
||||||
|
*Reported, not fixed:* in `hotwords_cache_promote()`, if `word` is NULL **and** the
|
||||||
|
cache is full, the guard at `:363` falls into the inner branch at `:364` and writes
|
||||||
|
NULL into `cache[lru_index]`. Unreachable today — every caller passes a non-NULL entry
|
||||||
|
from the bucket search — but the NULL check reads as though it prevents this, and does
|
||||||
|
not.
|
||||||
|
3. **ACL reap** — §9 still marks this `?`. ACL entries carry `acl_ttl` in `DictEntry`
|
||||||
|
already, so this is likely the easiest of the four to close, and it should be closed on
|
||||||
|
paper alongside the others rather than left dangling.
|
||||||
|
4. Does a patron ever change what it is? A block that is written becomes a new block by
|
||||||
|
content-addressing. A word that is redefined is a new word. If nothing mutates in place,
|
||||||
|
that is worth stating explicitly — it removes a whole class of proof obligation in §13.
|
||||||
|
|
||||||
|
### 17.5 The framebuffer is not a patron — it is a utility
|
||||||
|
|
||||||
|
**DECIDED.** This is §5 and §2 applied rather than a new call, but it was close enough to
|
||||||
|
becoming an exception that it is worth writing down explicitly.
|
||||||
|
|
||||||
|
#### Outside the Stadium is not the same as an exception
|
||||||
|
|
||||||
|
§11's warning is about a *patron kind that needs special handling inside the engine* — you
|
||||||
|
end up carrying the general machinery and the carve-out, and every future reader has to
|
||||||
|
learn both. That is the thing to fear, and the fear is correct.
|
||||||
|
|
||||||
|
But §5 is not a carve-out. It is a taxonomy. The test for whether something is an
|
||||||
|
exception is: **does the engine change because this thing exists?** For the framebuffer,
|
||||||
|
nothing changes. The engine never learns about it. That is a boundary, not an exception.
|
||||||
|
|
||||||
|
#### It fails §2's liveness test by definition, not by fiat
|
||||||
|
|
||||||
|
§2's scoping decision is the sharpest line in this document: *the Stadium holds what is
|
||||||
|
live, not everything that exists.* A patron arrives and departs. The framebuffer does
|
||||||
|
neither — it is there from init to power-off. It has no arrival event and no reap event,
|
||||||
|
not because it has been excused from having them, but because it genuinely has none.
|
||||||
|
|
||||||
|
#### Better than "the building's lighting": a utility
|
||||||
|
|
||||||
|
§5 calls the framebuffer the building's lighting, which undersells it — that reads like
|
||||||
|
part of the structure. It is closer to **the power company**: external infrastructure the
|
||||||
|
building consumes. Not the Stadium. Not the basement of the Stadium. A third thing.
|
||||||
|
|
||||||
|
That gives three categories, all principled, none of them exceptions:
|
||||||
|
|
||||||
|
| Category | Relation | Example |
|
||||||
|
|---|---|---|
|
||||||
|
| Warehouse | beneath | Artemis, the dictionary (§17.3) |
|
||||||
|
| Stadium | the floor | patrons |
|
||||||
|
| Utility | beside | framebuffer, and devices generally |
|
||||||
|
|
||||||
|
#### What is live is the dirty event — and it is not a fifth patron kind
|
||||||
|
|
||||||
|
Run §9's two questions on it:
|
||||||
|
|
||||||
|
- **Heat means** — a region written often is hot. A scrolling log, a blinking cursor. A
|
||||||
|
static border is cold. Traffic confers heat, identically to everything else.
|
||||||
|
- **Reap is** — redraw. Consumed by being painted.
|
||||||
|
|
||||||
|
Consumed on delivery, carries a TTL, dies on arrival. **A dirty event is a message whose
|
||||||
|
recipient happens to be the framebuffer.** It does not extend the patron taxonomy; it is
|
||||||
|
the message patron with a different destination.
|
||||||
|
|
||||||
|
Which yields a symmetry worth keeping:
|
||||||
|
|
||||||
|
| Patron | Code field terminates at | Which lives |
|
||||||
|
|---|---|---|
|
||||||
|
| Block | Artemis | beneath |
|
||||||
|
| Dirty event | framebuffer | beside |
|
||||||
|
|
||||||
|
Both are code fields finishing outside the Stadium. Neither is special.
|
||||||
|
|
||||||
|
**This closes the last `?` in §9.** The screen-cell row resolves to: the event is the
|
||||||
|
patron, the grid is not.
|
||||||
|
|
||||||
|
#### The sizing argument, independently
|
||||||
|
|
||||||
|
A framebuffer is several megabytes of fixed device memory. Making it a patron means either
|
||||||
|
blowing the bounded capacity that gives K a fixed denominator (§2), or forcing the
|
||||||
|
by-reference payload path to exist for exactly one pathological object — which would decide
|
||||||
|
§12 Q1's payload threshold on the worst possible case. Sizing a design around its single
|
||||||
|
largest outlier is how the header ends up wrong for the other ten thousand entries.
|
||||||
|
|
||||||
|
#### Not a patron does not mean no physics
|
||||||
|
|
||||||
|
Worth stating so it is not lost: excluding the framebuffer from the Stadium says nothing
|
||||||
|
about whether compudynamic concepts apply *within* it. A utility can have its own internal
|
||||||
|
dynamics — heat over regions, decay, adaptive refresh — without being a Stadium
|
||||||
|
participant. The power company has physics too.
|
||||||
|
|
||||||
|
**OPEN, deferred.** What those dynamics are is a question for when the framebuffer work
|
||||||
|
actually happens. It does not gate the Stadium, and it should not be designed speculatively
|
||||||
|
now.
|
||||||
|
|
||||||
|
### 17.6 Sizing and allocation — the Stadium should be dynamic, but not heap-allocated
|
||||||
|
|
||||||
|
§3 says the Stadium stays an array with index links. That is right, but it is stated in a
|
||||||
|
way that invites the wrong objection, because **"array" and "fixed at compile time" are
|
||||||
|
not the same thing** — and it is the second one that is genuinely objectionable.
|
||||||
|
|
||||||
|
A hardcoded capacity is arbitrary: `HOTWORDS_CACHE_SIZE = 32` is a number someone picked,
|
||||||
|
and §17.4 shows exactly how that ages. A contiguous block of fixed-size cells, sized at
|
||||||
|
boot from the memory budget and addressed by index, is dynamic in every sense that matters
|
||||||
|
operationally while remaining an array in every sense §3 and §13 depend on.
|
||||||
|
|
||||||
|
Four positions, with what each costs:
|
||||||
|
|
||||||
|
| | What it is | Cost |
|
||||||
|
|---|---|---|
|
||||||
|
| a | Capacity fixed at compile time | Arbitrary bound. What the hot-words cache does today. |
|
||||||
|
| **b** | **Sized at boot, contiguous, index-linked** | **None. Retains every property below.** |
|
||||||
|
| c | Contiguous but resizable at runtime | K's denominator moves; couples to §7 |
|
||||||
|
| d | Per-entry allocation, pointer links | Forfeits §13 |
|
||||||
|
|
||||||
|
#### Why (b) is free
|
||||||
|
|
||||||
|
The Stadium is established before any VM exists (§6), so boot is already the moment its
|
||||||
|
capacity is determined. Deriving that capacity from available memory rather than from a
|
||||||
|
constant costs nothing and gives up nothing. Cells stay uniform, links stay indices, the
|
||||||
|
region stays contiguous.
|
||||||
|
|
||||||
|
**LEANING toward (b).**
|
||||||
|
|
||||||
|
#### Why (d) is expensive — by this document's own argument
|
||||||
|
|
||||||
|
§13 is unambiguous:
|
||||||
|
|
||||||
|
> *No pointers. Fixed-size cells with index links means the arena models as a total
|
||||||
|
> function over a finite index set — no heap model, no separation logic, no aliasing, no
|
||||||
|
> null. **This is the single biggest difference between a tractable proof effort and a
|
||||||
|
> research project.***
|
||||||
|
|
||||||
|
Per-entry heap allocation gives that up and takes several things with it:
|
||||||
|
|
||||||
|
- **The finite state space.** Bounded capacity is what makes induction over the Stadium
|
||||||
|
straightforward and what puts model checking on the table alongside theorem proving.
|
||||||
|
- **§2's hard outer wall.** Without an inescapable bound, K is bookkeeping rather than a
|
||||||
|
conservation law — §2 says this in as many words.
|
||||||
|
- **The engine's simplicity.** This is a freestanding kernel with `kmalloc.c` / `pmm.c`
|
||||||
|
and no libc. Allocation in the reap path means the engine can fail to allocate, which
|
||||||
|
means the engine needs a failure mode, which means it is no longer the thing §3
|
||||||
|
describes. An engine that can fail is a different engine.
|
||||||
|
|
||||||
|
Fragmentation is the least of it, though §3 is right that indices avoid that too.
|
||||||
|
|
||||||
|
#### Why (c) is the genuinely open one
|
||||||
|
|
||||||
|
A contiguous region that grows and shrinks *as a whole* keeps index links and keeps the
|
||||||
|
proof structure — the capacity becomes a parameter rather than a constant, which HOL
|
||||||
|
handles without difficulty. What it complicates is K, since the denominator moves.
|
||||||
|
|
||||||
|
This is not a new question. §7 already has it open for per-VM shares: *"whether a VM's
|
||||||
|
share is a hard bound or an elastic one that can grow and shrink under pressure, with
|
||||||
|
capacity transferring between VMs as a conserved operation Hera arbitrates."* Elasticity at
|
||||||
|
the Stadium level and elasticity at the per-VM level are the same question asked at two
|
||||||
|
scales, and they should be answered together rather than separately.
|
||||||
|
|
||||||
|
**OPEN**, and coupled to §7 and to §12 Q6 (one Stadium or nested per VM). Note that if Q6
|
||||||
|
resolves to nested-per-VM, (c) becomes considerably more attractive — capacity transfer
|
||||||
|
between VMs is the whole point of that arrangement, and a fixed per-VM bound would waste it.
|
||||||
|
|
||||||
|
#### The rule this reduces to
|
||||||
|
|
||||||
|
> **Dynamic in capacity. Static in structure.**
|
||||||
|
|
||||||
|
Decide how big the Stadium is at runtime. Do not decide what an entry is, or how entries
|
||||||
|
are addressed, at runtime.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 18. The engine — L0
|
||||||
|
|
||||||
|
The engine that holds the patrons is a loop like the others, and it needs a name in the
|
||||||
|
same scheme. L1–L7 are taken by the existing feedback loops; L8 is the Jacquard mode
|
||||||
|
selector. The engine sits **beneath** all of them, so: **L0**.
|
||||||
|
|
||||||
|
### 18.1 L0 and L8 bookend the gated loops
|
||||||
|
|
||||||
|
This produces a structure worth drawing, because it explains why two of the ten are
|
||||||
|
different in kind:
|
||||||
|
|
||||||
|
```
|
||||||
|
L8 Jacquard mode selector always on, ungated
|
||||||
|
─────────────────────────────────────────────────────
|
||||||
|
L1 … L7 feedback loops gated by L8
|
||||||
|
─────────────────────────────────────────────────────
|
||||||
|
L0 the Stadium engine always on, ungated
|
||||||
|
```
|
||||||
|
|
||||||
|
L1–L7 are gated: L8 switches them on and off, 128 configurations over seven bits.
|
||||||
|
|
||||||
|
The two bookends are ungated, and for symmetric reasons:
|
||||||
|
|
||||||
|
- **L8 cannot be gated** because something has to decide the gates. A selector that could
|
||||||
|
deselect itself has no defined behaviour.
|
||||||
|
- **L0 cannot be gated** because it is what holds the patrons the other loops operate on.
|
||||||
|
Switch it off and nothing is reaped, the Stadium fills and stays full, and K stops being
|
||||||
|
conserved. That is not a mode, it is a failure state.
|
||||||
|
|
||||||
|
This is the same argument §6 makes about boot order. The thing that manages existence
|
||||||
|
cannot be a participant in what it manages — not for VMs, and not for loops.
|
||||||
|
|
||||||
|
**DECIDED.**
|
||||||
|
|
||||||
|
### 18.2 The Jacquard accounting is an exclusion, not an extension
|
||||||
|
|
||||||
|
The obvious reading of "add L0" is that the selector grows a bit: 7 bits becomes 8,
|
||||||
|
128 configurations become 256.
|
||||||
|
|
||||||
|
**That is the wrong move, and §18.1 is why.** L0 is not gateable, so it has no bit. The
|
||||||
|
gate word stays seven bits wide and the selector stays at 128 states.
|
||||||
|
|
||||||
|
This is worth stating explicitly because the alternative is expensive: widening the gate
|
||||||
|
word would invalidate the 128-configuration L8 table, the DoE campaign already run against
|
||||||
|
it, and the existing results. There is no reason to pay that, and the design does not ask
|
||||||
|
us to.
|
||||||
|
|
||||||
|
> **L0 is accounted for in Jacquard by being deliberately absent from it.**
|
||||||
|
|
||||||
|
**DECIDED.**
|
||||||
|
|
||||||
|
### 18.3 Dispatch: enumerate behaviours, not kinds
|
||||||
|
|
||||||
|
§13 already requires a closed enumeration:
|
||||||
|
|
||||||
|
> *The set of code-field behaviours must be a closed enumeration, fixed at build time…
|
||||||
|
> Model it as a datatype of behaviour tags plus a dispatch function and the whole thing
|
||||||
|
> stays first-order and tractable.*
|
||||||
|
|
||||||
|
So a fixed enum with fixed dispatch is mandatory, not a concession to practicality. But
|
||||||
|
there are two things one could enumerate, and only one of them preserves §3:
|
||||||
|
|
||||||
|
| | Enumerate | Engine asks | Cost of a fifth patron |
|
||||||
|
|---|---|---|---|
|
||||||
|
| ✗ | patron **kinds** — `BLOCK`, `WORD`, `ACL`, `MESSAGE` | "what are you?" | touch the engine |
|
||||||
|
| ✓ | **behaviours** — `MIGRATE`, `DELIVER`, `EXPIRE`, `COOL` | nothing; calls `dispatch(tag)` | none |
|
||||||
|
|
||||||
|
Both are closed, both are fixed at build time, both are equally provable. Only the second
|
||||||
|
keeps the engine ignorant of its contents, which is the property §3 exists to protect. Two
|
||||||
|
patrons may share a tag; a new patron that migrates costs zero engine changes.
|
||||||
|
|
||||||
|
The branching Captain Bob is right to want is real and it is allowed — it lives in the
|
||||||
|
dispatch function over a closed tag set, not in the engine asking patrons what they are.
|
||||||
|
|
||||||
|
**DECIDED.**
|
||||||
|
|
||||||
|
### 18.4 One tick
|
||||||
|
|
||||||
|
L0 advances on the adaptive heartbeat. Everything derived from time is derived from that
|
||||||
|
one counter:
|
||||||
|
|
||||||
|
- TTL decrements per tick (messages, ACLs)
|
||||||
|
- Heat decays per tick (blocks, words)
|
||||||
|
|
||||||
|
Two measures, one clock — see §17.1. §16.4 forces this: the engine must fire on tick count
|
||||||
|
for the same input to reproduce the same dictionary hash, and two independent time sources
|
||||||
|
would be two independent sources of drift.
|
||||||
|
|
||||||
|
### 18.5 CLOSED — the adaptive rate does not break determinism, and here is why
|
||||||
|
|
||||||
|
The concern: the heartbeat is *adaptive* — faster, slower, window wider, narrower. If it
|
||||||
|
adapts off **timing measurements**, the adaptation is machine-dependent and §16.4 fails.
|
||||||
|
If it adapts off **execution-derived state**, tick ordinals still map deterministically to
|
||||||
|
work and parity survives.
|
||||||
|
|
||||||
|
Traced end to end on 2026-08-03. **The dictionary-parity chain is clean.** Resolution (1)
|
||||||
|
— adaptation inputs are execution-derived, TIME-TRUST stays diagnostic — is already the
|
||||||
|
de-facto design.
|
||||||
|
|
||||||
|
Evidence, in the order it decides the question:
|
||||||
|
|
||||||
|
1. **TIME-TRUST is computed and never consumed.** `heartbeat_trust()` has **zero callers**
|
||||||
|
in the entire tree. `m5_time_trust` and `m5_variance` (`include/vm.h:315-316`) are
|
||||||
|
declared and never read or written. The only consumer of `ts->trust` is
|
||||||
|
`starkernel/doe_log.c:98`, which writes it to a CSV column. It is measured and
|
||||||
|
reported, never fed back.
|
||||||
|
|
||||||
|
2. **The intent is already documented.** `include/starkernel/timer.h:70` —
|
||||||
|
*"TIME-TRUST thresholds in Q48.16 (for diagnostics, NOT for gating)."*
|
||||||
|
|
||||||
|
3. **Every inference-engine input is execution-derived.** `vm_runtime.c:626-640` populates
|
||||||
|
`InferenceInputs` from: the rolling window, `trajectory_length` (from `window_pos` /
|
||||||
|
`total_executions`), `prefetch_hits` / `prefetch_attempts`, `hot_word_count`,
|
||||||
|
`stale_word_count`, `total_heat`, `word_count`, and the previous check's baselines.
|
||||||
|
**No timing input of any kind.** The outputs it applies — `adaptive_window_width` and
|
||||||
|
`adaptive_decay_slope` — therefore depend only on execution history.
|
||||||
|
|
||||||
|
4. **Decay is tick-based, and deliberately so.** `vm_tick_apply_background_decay()` is
|
||||||
|
handed `vm_monotonic_ns(vm)` but computes
|
||||||
|
`elapsed_ticks = tick_count - last_decay_tick` (`vm_runtime.c:375`). The `now_ns`
|
||||||
|
argument only writes `last_decay_ns`. The comment at `:373-374` says so explicitly:
|
||||||
|
*"Tick-based, not wall-clock… now_ns is kept only to refresh last_decay_ns for
|
||||||
|
diagnostics."* Someone already defended this exact boundary.
|
||||||
|
|
||||||
|
5. **The parity hash contains nothing time-derived.** `capsule_dict_hash_hook()`
|
||||||
|
(`capsule/capsule_vm_hooks.c:60-70`) walks the dictionary hashing exactly two things
|
||||||
|
per entry: **the word name and `execution_heat`**. Not `last_decay_ns`, not any
|
||||||
|
timestamp. So even the diagnostic wall-clock field from (4) cannot reach the hash.
|
||||||
|
|
||||||
|
**Conclusion: §16.4 holds today, and holds by construction rather than by luck.**
|
||||||
|
|
||||||
|
#### One real exception, and it is not in the parity path
|
||||||
|
|
||||||
|
`vm_physics_touch()` (`capsule/capsule_vm_physics.c:250-313`) **is** wall-clock dependent:
|
||||||
|
it computes `elapsed_us = (now_ns - last_active_ns) / 1000` (`:272`) and the header comment
|
||||||
|
at `:122` confirms the transfer amount scales with elapsed time. So **fleet-level VM heat
|
||||||
|
is not reproducible run to run** the way dictionary heat is.
|
||||||
|
|
||||||
|
Scope of that, precisely:
|
||||||
|
|
||||||
|
- It touches `node->physics` in the VM registry, **not** `DictEntry.execution_heat`, so it
|
||||||
|
does not reach the parity hash and does not invalidate the existing claim.
|
||||||
|
- `vm_physics_tick()` (`:366`) explicitly discards its `now_ns` argument (`(void)now_ns;`),
|
||||||
|
so only the touch path is affected.
|
||||||
|
- With Hera alone this is nearly inert. It becomes live again when Hermes and Artemis
|
||||||
|
return.
|
||||||
|
|
||||||
|
This is a **pre-existing condition, not something the Stadium introduces.** But it is
|
||||||
|
exactly the pattern L0 must not inherit, and it is worth knowing that fleet K figures and
|
||||||
|
dictionary parity have different reproducibility guarantees today.
|
||||||
|
|
||||||
|
#### The invariant this should become
|
||||||
|
|
||||||
|
Determinism currently survives on convention plus one good comment. That is too thin for
|
||||||
|
something load-bearing. L0 should make it explicit:
|
||||||
|
|
||||||
|
> **Anything that influences patron state advances on tick count. Wall-clock time may be
|
||||||
|
> recorded for diagnostics and must never be an input to a decision.**
|
||||||
|
|
||||||
|
**DECIDED**, and it supersedes the "leaning (1)" in the earlier draft of this section.
|
||||||
Reference in New Issue
Block a user