Captain Bob, 2026-10-05. For each word on a node the kernel keeps v3's own DictEntry record, joined by word ID; v3's physics, heartbeat, ACL words, Stadium word layer and parity run on the records unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
659 lines
34 KiB
Markdown
659 lines
34 KiB
Markdown
# StarForth v4.0.0 — Parity with v3 up to the first prompt
|
||
|
||
Started 2026-10-05. This file measures v4 against one ruling:
|
||
|
||
> v4 is exactly like v3 in functional requirements up to the first FORTH
|
||
> prompt. That is the stopping point for now. (Captain Bob, 2026-10-05)
|
||
|
||
and a second, given the same day:
|
||
|
||
> I would never stub, simulate or develop outside of functional,
|
||
> as-intended code. Piecemeal is how errors and intent get hidden.
|
||
|
||
It records what v3 does, where in v3's code, what v4 does today, and what
|
||
has to be ruled before v4 can do the same. It proposes; it decides nothing.
|
||
Nothing described here as missing has been built.
|
||
|
||
## 1. What v3 does before its first prompt
|
||
|
||
From the bare-metal boot log `logs/20261003-100613/amd64/`, in order, after
|
||
the kernel's M0–M6 hardware milestones:
|
||
|
||
| # | v3 | v4 today |
|
||
|---|---|---|
|
||
| 1 | Stadium allocated (cells, 50 VM slots); switch-signal and kernel-Hermes channel tables | Missing |
|
||
| 2 | VM arena; 530 words registered | One node; 295 assembled words and 2 from the capsule |
|
||
| 3 | Physics live whenever a word executes: per-word heat, decay, rolling window, pipelining, L8 mode selector (61 mode changes during POST), heartbeat | Missing; see section 2 |
|
||
| 4 | POST: 1050 tests, every module (FORTH-79, StarForth extensions, ACL, Mama, Q48, inference, physics freeze) | 550 cases, FORTH-79 Required Word Set only |
|
||
| 5 | `PARITY:M7.1a` word count, here, latest, dictionary hash; `PARITY:OK`; `POST: PASSED` | Present, as `PARITY:V4_*` |
|
||
| 6 | PCI; virtio-blk attached; Artemis genesis signature | Missing. Blocks are a RAM array |
|
||
| 7 | Mama init capsule `init.4th`: signature checked, executed, `PARITY:MAMA_INIT` | v4 loads its own two capsules the same way; not `init.4th` |
|
||
| 8 | ACL: `BIRTH` and `CAPSULE-BIRTH` pinned strict | Missing. The ACL words are assembled but not loaded |
|
||
| 9 | Heartbeat running; Stadium conservation checks; quota and kernel-Hermes self-tests | Missing |
|
||
| 10 | Hestia born, then Artemis (disk formatted, self-test, K figures), each with `PARITY:BIRTH` | Missing. One node, no fleet |
|
||
| 11 | Kernel-Hermes fleet self-tests; USB mass storage; Zuse genesis minted on the thumbdrive | Missing |
|
||
| 12 | Banner with versions; `[zuse@Hera] ok>` | `ok>` alone |
|
||
|
||
Rows 1, 6, 9, 10 and 11 are messaging, blocks and the console's wider
|
||
setting. They are to be discussed before anything is written about them.
|
||
Section 2 is row 3.
|
||
|
||
## 1a. Correction (2026-10-05): the table above is wrong about "Missing"
|
||
|
||
Captain Bob: "You haven't looked at the codebase. You're assuming missing
|
||
pieces that are not missing and ignoring the entire v3 architecture. Don't
|
||
look at pieces until you see how they fit."
|
||
|
||
The table was made from a boot log and a few functions. Read as a whole,
|
||
the system is this.
|
||
|
||
**How v3 fits together.** After the hardware milestones, `kernel_main.c`
|
||
(`kernel_main_deep`, from line 526) does, in order:
|
||
|
||
1. Fleet tables, before any VM exists: Stadium, sessions, switch-signal,
|
||
kernel-Hermes channels and queues; Hera made patron zero.
|
||
2. `sk_vm_bootstrap_parity()` (`kernel/src/vm/bootstrap/sk_vm_bootstrap.c`):
|
||
one `VM` is initialised with the kernel's host services; the capsule
|
||
hooks and the VM registry are set up; its fleet physics is seeded
|
||
(`vm_physics_init`); the kernel's own words are registered (`BIRTH`,
|
||
`KILL`, the capsule words); the block subsystem is given to it; POST
|
||
runs; parity is collected and printed.
|
||
3. Devices: block RAM and ramdrive, PCI, entropy, Artemis's virtio disk and
|
||
its signature, keyboard, USB, framebuffer.
|
||
4. The capsule directory is copied and `capsule_birth_mama()` runs
|
||
`init.4th` on that VM; `BIRTH` and `CAPSULE-BIRTH` are pinned.
|
||
5. The timer starts: the heartbeat.
|
||
6. Stadium and kernel-Hermes self-tests; Hestia and Artemis are born, each
|
||
another `VM`; Zuse; the banner; the REPL.
|
||
|
||
Every one of those steps is kernel code in `kernel/src` and works on a
|
||
`VM` through one interface: the `VM` structure (`v3/include/vm.h`) and the
|
||
functions on it. Counted across the kernel's services, the most used are
|
||
the stacks (`vm_push`, `vm_pop`), `vm_interpret`, `vm_find_word`, the
|
||
dictionary (`latest`, `here`, `DictEntry`), `error`, the heartbeat state,
|
||
the rolling window, and `stadium_vm_id`.
|
||
|
||
**What v4 is, by its own justification** (`JUSTIFICATION.md` section 16):
|
||
"The only difference is the machine underneath: the F18-derived engine
|
||
instead of the original StarForth VM." And section 15: StarshipOS is
|
||
"LithosAnanke running the v4 F18 engine", with the FORTH-79 vocabulary and
|
||
"the StarshipOS-specific portions" stored as capsules.
|
||
|
||
**So nothing in rows 1, 6, 7, 8, 9, 10, 11 or 12 is missing.** It is all
|
||
in this kernel and it all runs today. What is wrong is where v4 was put:
|
||
`kernel_main.c` calls `sk_v4_run()` *before* step 1 and it never returns.
|
||
The v4 node comes up beside the system instead of inside it, and so the
|
||
whole of the architecture is skipped. That is the detour. The capsule
|
||
loader, POST and parity lines built on 2026-10-05 (`v4/system/boot.c`)
|
||
repeat, for a lone node, things steps 2 and 4 already do for a `VM`.
|
||
|
||
**The real question** is therefore not "what does v4 lack" but "how does
|
||
the F18 engine take the place of the StarForth VM behind that one
|
||
interface", so that steps 1 to 6 run as they do now. That has not been
|
||
worked out, and nothing here should be read as a plan for it.
|
||
|
||
Section 2 below was written before this correction. Its account of what
|
||
v3's inner loop does is from the code and stands. Its section 2.2, "what
|
||
v4 has", describes the lone node, not v4 in its place in the system.
|
||
|
||
## 1b. Console (discussed and ruled 2026-10-05)
|
||
|
||
**v3, as built** (`FABRIC-3.5.md` sections XVII and XVIII):
|
||
|
||
- Layer 0, the fabric: UART, framebuffer, VT100, font, in kernel C
|
||
(`kernel/src/hal`). Singular, permanent, and it never knows a VM exists;
|
||
anything VM-shaped reaches it by a registered callback.
|
||
- Layer 1, Hestia: the Tripod leg that owns console policy and is the bind
|
||
point. Her vocabulary is the drawing words.
|
||
- Layer 2, console proxies: one VM per attached user, born at `WIREBIND`;
|
||
their traffic goes through kernel-Hermes.
|
||
- Input belongs to the kernel. `kernel/src/repl.c` reads the serial port and
|
||
the keyboard, echoes, edits and builds the line, then hands the whole line
|
||
to the VM `USE` has made active (`vm_interpret`), or sends it through
|
||
kernel-Hermes. It services the heartbeat while idle.
|
||
- Output: a VM's `EMIT` goes to the host service `putc`, then
|
||
`console_putc`. The fabric puts `[user@VM]` before each line. The prompt
|
||
is the REPL's, not the VM's.
|
||
|
||
A v3 VM never reads its own command line and never prints its own prompt.
|
||
|
||
**Ruling (Captain Bob, "for now, yeah"):** the kernel hands a v4 node a
|
||
whole line and takes characters back, exactly as it does a v3 VM. The
|
||
node's own prompt loop plays no part at that level. Layers 0, 1 and 2 and
|
||
the kernel's REPL stay as they are. `KEY`, `EXPECT`, `QUERY` and `QUIT`
|
||
remain FORTH-79 words but are not how the kernel drives a node.
|
||
|
||
"For now": `DECOMPOSITION.md` makes `EMIT` and `KEY` messages to a console
|
||
device node, which in the mesh is Hestia's part. That is the destination,
|
||
not this step.
|
||
|
||
**What this says about the lone-node boot.** It has the node run its own
|
||
`QUIT` loop, print its own prompt and ` ok`, and wait in `KEY`;
|
||
`v4/system/boot.c` then takes the ` ok` and prompt back out of what the
|
||
node prints. That is a work-around for the node owning what the kernel
|
||
owns, and it goes when the engine takes the VM's place.
|
||
|
||
## 1c. The frame: the Stadium
|
||
|
||
`FABRIC-2.md` section B: "words are stadium patrons, VMs are patrons,
|
||
blocks are patrons, messages are stadium patrons, all should be operated on
|
||
by THE SAME ENGINE."
|
||
|
||
- A patron is one 64-byte cell of nine wires: identity, heat, TTL, pin,
|
||
link, behaviour, mass, payload, contains
|
||
(`kernel/include/starkernel/vm/stadium.h`).
|
||
- Heat is a conserved share of 1.0; K is that conservation.
|
||
- The behaviour is what happens when a patron leaves: words and VMs `COOL`,
|
||
blocks `MIGRATE`, messages `DELIVER`, ACLs `EXPIRE`.
|
||
- A cell never holds the thing itself, only its identity and heat. A word's
|
||
code stays in the dictionary; a block's bytes stay in the block
|
||
subsystem.
|
||
|
||
Compudynamics is therefore the kernel's Stadium, not something inside the
|
||
VM engine. The engine's part is to report what it executes: in v3,
|
||
`stadium_word_dispatch(vm, word_id, tick)` from the inner loop.
|
||
|
||
**Reading of the opcode ruling (section 2.7) in this frame, stated to
|
||
Captain Bob 2026-10-05 and not corrected:** on v4 that report carries an
|
||
opcode, and a VM's word patrons become its 32 opcode patrons.
|
||
|
||
## 1d. Blocks (discussed and ruled 2026-10-05)
|
||
|
||
**v3, as built:**
|
||
|
||
- One block address space in kernel C (`v3/src/block_subsystem.c`): fast
|
||
RAM at 0 to 2047, the ramdrive, Artemis's virtio disk, USB drives,
|
||
chained.
|
||
- Every block has metadata (`blk_meta_t`): the owner's fingerprint,
|
||
`acl_allow`, `acl_ttl`, a write count, chain links.
|
||
- Blocks are patrons: `BLOCK`, `BUFFER` and `UPDATE` call
|
||
`stadium_block_dispatch` with the LBN; heat drives migration and wear
|
||
levelling (`kernel/src/vm/stadium_blocks.c`).
|
||
- Artemis is the Tripod leg that owns persistent storage.
|
||
- Identity lives there: a keypair Zuse mints onto a thumbdrive; its
|
||
fingerprint is stamped on every block it claims at first touch, behind
|
||
the home-blocks fence; the disk's genesis signature is how the kernel
|
||
knows Artemis's disk.
|
||
|
||
A v3 VM's block words ask the kernel's block subsystem for a block and get
|
||
a buffer back. Ownership, ACL, physics and devices are the kernel's.
|
||
|
||
**Ruling (Captain Bob, "yes"):** a v4 node only ever asks for a block by
|
||
number. Everything about who may have it is decided on the kernel's side,
|
||
under the VM's identity. The device chain, metadata, first-touch claim,
|
||
ACL, migration and the Stadium touch stay where they are.
|
||
|
||
**What this says about the lone-node boot.** It gives the node a bare RAM
|
||
array behind its four block registers (D-19), which skips the address
|
||
space, the metadata, the owner, the ACL and the Stadium.
|
||
|
||
## 1e. Messaging (discussed and ruled 2026-10-05)
|
||
|
||
**v3, as built** (`FABRIC-3.5.md` sections III, XLIII, XLV;
|
||
`kernel/include/starkernel/vm/kernel_hermes.h`):
|
||
|
||
- Hermes is the kernel, not a VM: the routing layer between the Stadium
|
||
floor and the HAL.
|
||
- A message is a patron. Its heat is the Stadium cell it occupies; sending
|
||
draws a slot's worth from the sender's reservoir; a ledger of held,
|
||
pulled, returned and consumed is audited for conservation.
|
||
- Publish and subscribe: one permanent common channel every VM joins at
|
||
birth; a private channel by request and grant or deny, with ACK/NACK.
|
||
- Who may open a channel with whom is ACL policy: kernel-Hermes asks
|
||
`ACL.4th` and does not decide.
|
||
- A payload is FORTH text, one block at most; larger is chunked.
|
||
- Hermes delivers into no one. It queues the payload for the target and
|
||
publishes the fact. The target drains its own queue at its outermost
|
||
interpret checkpoint by interpreting the text; the switcher reads the
|
||
same fact to choose which VM runs.
|
||
|
||
**Ruling (Captain Bob, "yes"):** a v4 node receives by being handed text
|
||
to interpret, and sends by asking. Everything else stays in kernel-Hermes:
|
||
heat, channels, the ACL question, queues, the ledger, the switcher.
|
||
|
||
**Open.** v3's safe moment is a per-word checkpoint in the inner loop, at
|
||
the outermost interpret level. A v4 node has no inner loop. What a node's
|
||
safe moment is has to be defined; it is also where the switcher moves
|
||
control, so it decides how the fleet shares the processor.
|
||
|
||
**Intent stated, for later (Captain Bob, 2026-10-05):** "When Artemis time
|
||
comes, we're going to pull that up into the kernel as well." As Hermes
|
||
became kernel-Hermes. Not this step. Today Artemis the VM is
|
||
`capsules/artemis/init.4th`: 550 lines, 77 words — a flat-pool disk manager
|
||
(header, free map, allocator, format and resume, Stadium admission for
|
||
allocated blocks, K total, cooling, reap, self-tests).
|
||
|
||
## 1f. ACLs (discussed 2026-10-05; the word card is to change)
|
||
|
||
**v3, as built** (`FABRIC-2.md` section H.3): identity carries its ACL as a
|
||
stack of cards with pinholes through them. Four cards, a closed set: VM,
|
||
word, block, message. An action consults only the cards for the dimensions
|
||
it touches.
|
||
|
||
- Word card: the four fields in each dictionary entry (`acl_ttl`,
|
||
`acl_allow`, `acl_mode`, `acl_pinned`); per VM, since each VM has its own
|
||
dictionary. Checked at every word dispatch in the inner loop.
|
||
- Block card: `acl_allow` and `acl_ttl` in each block's metadata; policy in
|
||
`block-acl.4th`.
|
||
- Message card: the channel-open question kernel-Hermes puts to `ACL.4th`.
|
||
- VM card: the VM-level dimension.
|
||
- Creator ceiling: a child's ACL state is a snapshot of its parent's at
|
||
birth.
|
||
- Policy is FORTH; the fields and the hot-path check are C.
|
||
- With physics: a word's TTL is computed from its heat
|
||
(`heat/4 + 256`, capped), and an ACL is the patron whose leaving is
|
||
`EXPIRE`.
|
||
|
||
The block, message and VM cards are decided on the kernel's side and carry
|
||
over under sections 1d and 1e. The word card does not: a v4 node has no
|
||
dispatch point at which to check it, and its TTL comes from per-word heat,
|
||
which v4 has not got (section 2.7).
|
||
|
||
**Captain Bob, 2026-10-05,** asked whether the word card stays per word:
|
||
"I think it would be wise to change that as well, because as we move down
|
||
our little ladder here into the FPGA area, this is going to be where
|
||
pretty much our division point is going to be. We're going to block things
|
||
off and we're going to build little teeny teeny machines everywhere on the
|
||
fabric."
|
||
|
||
**Reversed the same day:** "Now that I think about it, let's leave it
|
||
exactly as we're doing it in v3 — if we can figure out where to hook into
|
||
it to measure that."
|
||
|
||
So the word card stays per word, as v3, provided a v4 node has a place to
|
||
check it.
|
||
|
||
**Where a v4 node can hook, from the code:**
|
||
|
||
- There is one. Every call of one word by another is the `call` opcode,
|
||
executed at one place in the engine: `v4/src/exec.c`, `case V4_OP_CALL`.
|
||
The node has the target's address there, before control moves. It is the
|
||
line where the per-call-target count was taken.
|
||
- The compiler lays down a `call` for every word that is not in-line
|
||
(`(CALL,)`, `v4/capsule/compile.v4:100`) and never turns a call into a
|
||
jump, so definitions compiled on the node all pass through it.
|
||
|
||
**What does not pass through it:**
|
||
|
||
- **In-line words.** 62 words are compiled as opcodes, not called, among
|
||
them `DUP DROP OVER SWAP ROT + - AND XOR OR @ ! +! 1+ 1- 2+ 2- NEGATE`.
|
||
v3 checks every one of these at every execution. Six must stay in line
|
||
whatever is decided, because a call would bury the return address:
|
||
`>R R> R@ I J LEAVE`.
|
||
- **`EXECUTE`.** It is `push ;` — the word is entered by a return, not a
|
||
call.
|
||
- **Hand-written jumps.** The assembled nucleus ends many words by jumping
|
||
to another (`: CR 10 jump EMIT`). A colon definition in a capsule does
|
||
not, so this shrinks as words move to the capsule.
|
||
- **Telling a word from a mere address.** Not every call target is a
|
||
dictionary entry: the assembled files define more routines than they
|
||
give headers to. The node must be able to tell, at the call, whether the
|
||
target has the three cells of an entry before it.
|
||
|
||
**Intent, stated by Captain Bob 2026-10-05:** "Intent is for a runtime
|
||
check of a word. And the way we were originally doing it is the TTL was how
|
||
often the check would be performed. If the TTL was still valid, the word
|
||
would not get checked, in order to save the overhead of checking for
|
||
permissions every time."
|
||
|
||
So: the check is at run time, per word, at the hook. The countdown is the
|
||
cheap part, done on every execution; the permission check itself is done
|
||
only when the countdown reaches zero. A check made only when a definition
|
||
is compiled does not meet the intent.
|
||
|
||
What follows for the in-line words: to be checked at run time a word has
|
||
to pass through the hook, so it has to be called. The six that cannot be
|
||
called (`>R R> R@ I J LEAVE`) are the residue: they can only appear inside
|
||
a definition, and can only be checked when it is compiled.
|
||
|
||
**The TTL is adaptive, as v3 (Captain Bob, 2026-10-05):** "A fixed TTL
|
||
would make no sense whatsoever. The hotter the word, the more frequently
|
||
it's checked."
|
||
|
||
v3's policy, `capsules/ACL.4th` block 4003: a word's TTL is its own heat
|
||
divided by 4, plus 256, capped — the number of executions until its next
|
||
check. A hot word runs through its TTL sooner and so is checked more often
|
||
than a cold one, while each check is paid for by more executions.
|
||
|
||
So a v4 node counts executions **per word**, at the call hook, for the
|
||
ACL's use, and `ACL-TTL-COMPUTE` reads that count as it does in v3. This is
|
||
a second count beside the per-opcode heat of section 2.7; in v3 the two are
|
||
one field.
|
||
|
||
**Not yet settled, and not to be assumed:**
|
||
|
||
- Whether the per-word count decays as v3's word heat does (Loop #3), and
|
||
whether a word is then a Stadium patron as in v3 or only the 32 opcodes
|
||
are.
|
||
- How the node tells a dictionary entry from a bare address at the call.
|
||
- `EXECUTE`, which enters a word by a return.
|
||
|
||
## 1g. Correction: every kind of patron keeps its own accounts
|
||
|
||
Captain Bob, 2026-10-05: "You're looking at it all wrong. Think of v3. The
|
||
VM is not the same thing as a word. It has a different set of accounting
|
||
rules in the Stadium. Same for any other patron. They define their own
|
||
accounting rules. Look at how the code is written."
|
||
|
||
Sections 1f and 2 above treat heat as one number per thing and ask where
|
||
to put it. That is wrong, and the code shows it. The generic engine
|
||
(`kernel/src/vm/stadium.c`) knows only a cell, a VM's quota and reservoir,
|
||
admission, eviction and the conservation check. What a patron *is*, and
|
||
how it is charged, is defined by its own layer:
|
||
|
||
| Patron | Layer | Its own rules |
|
||
|---|---|---|
|
||
| VM | `kernel/src/capsule/capsule_vm_physics.c`; quotas in `stadium.c` | Fleet-wide: the heat of all live VMs sums to 1.0. Born at zero. Touched only at dispatch points (`BIRTH`, `KILL`, `VM-EXEC`, `VM-CALL`, `VM-STEP`), which pull heat from the rest of the fleet toward it by elapsed ticks times a slope. On death its heat goes up the parent chain to Hera. Its Stadium quota and reservoir are granted from its parent. |
|
||
| Word | `kernel/src/vm/stadium_words.c` | One cell per (VM, word ID). Admitted on first dispatch with a starter grant. Every dispatch pulls a quantum from the VM's reservoir, never below a floor of one third. Cools by a fraction of its own heat per tick, back to the reservoir. Unpinned. Leaves by `COOL`. |
|
||
| Block | `kernel/src/vm/stadium_blocks.c` | One cell per (VM, LBN), in a hash table since LBNs are sparse. Its own quantum and cooling rate. Leaves by `MIGRATE`. |
|
||
| Message | `kernel/src/vm/kernel_hermes.c` | Admission costs the sender one slot's share. Decays by a fixed factor each time it is applied, and what decays is *consumed*, not returned. A ledger of held, pulled, returned and consumed must balance exactly. |
|
||
|
||
Each layer's constants are its own Kconfig symbols
|
||
(`STADIUM_WORD_HEAT_QUANTUM`, `STADIUM_WORD_COOL_RATE_Q48`,
|
||
`STADIUM_BLOCK_HEAT_QUANTUM`, `STADIUM_BLOCK_COOL_RATE_Q48`,
|
||
`STADIUM_CAPACITY_TICK`). Per VM, resident heat plus reservoir is 1.0.
|
||
|
||
**A v3 word already has more than one account.** Its Stadium cell is one.
|
||
The `execution_heat` field of its dictionary entry is another, with a
|
||
different rule (a flat amount per tick, not a fraction), and
|
||
`stadium_words.c` says of admission: "execution_heat plays no role". The
|
||
ACL's TTL reads `execution_heat`.
|
||
|
||
So these earlier statements are withdrawn:
|
||
|
||
- "A v4 node has two counts where v3 has one field" (section 1f). v3 has
|
||
separate accounts already; that is the design, not an anomaly.
|
||
- "Whether a word is a Stadium patron or only the 32 opcodes are" (section
|
||
1f, open list). It is not either-or. The opcode is a kind of patron with
|
||
rules of its own, as the word, the VM, the block and the message each
|
||
are.
|
||
|
||
What is actually open is what the opcode's own rules are.
|
||
|
||
## 1h. The opcode's accounts: left unwired for now (2026-10-05)
|
||
|
||
Asked what the opcode's own accounting rules are, Captain Bob: "For the
|
||
moment, I think we're going to leave it unwired and kind of maybe just
|
||
make some observations on it and see if we even need it, as far as
|
||
opcodes are concerned. I think we will when we go to FPGA, but for now,
|
||
let's just hang on to the thought and make sure that it's available if
|
||
needed."
|
||
|
||
So:
|
||
|
||
- No opcode patron layer is built. Opcodes are not wired to the Stadium.
|
||
- The node goes on counting each opcode as it retires
|
||
(`v4_heat.op[32]`, `v4/src/heat.c`), and the anti-clock. Those counts
|
||
are real and are there to be observed. Nothing reads them to decide
|
||
anything.
|
||
- This amends section 2.7 and the reading in section 1c: a VM's word
|
||
patrons do **not** become opcode patrons now.
|
||
|
||
**Reading, stated to Captain Bob and to be corrected if wrong:** with
|
||
opcodes unwired and v4 = v3 functionally, a word on v4 keeps a word's
|
||
accounts as v3 has them — its Stadium cell by `stadium_words.c`'s rules,
|
||
and the `execution_heat` the ACL reads — taken at the call hook of
|
||
section 1f, which is where a v4 node dispatches a word.
|
||
|
||
## 1i. Asking the kernel; Hera as process manager (ruled 2026-10-05)
|
||
|
||
**v3, as built:** a kernel word is a C function `void f(VM *vm)` registered
|
||
by name (`register_word`); it takes its arguments from the VM's data stack
|
||
and leaves its results there. Some interpret text on the same VM while
|
||
they run (`EXEC`), saving and restoring the interpreter's state around it.
|
||
|
||
**The v4 design already has the carrier** (`DECOMPOSITION.md` section 6):
|
||
a node's ports are addresses; "a write blocks until the neighbour reads.
|
||
This is the GA144 model. No instruction is added."
|
||
|
||
**Put to Captain Bob:** a node asks the kernel by a blocking write to a
|
||
port, served between the node's instructions; a kernel word is an ordinary
|
||
dictionary entry whose body stores its request number to the port; kernel
|
||
words are made by handing the node text at boot.
|
||
|
||
**Ruling:** "Yes, and Hera will be the process manager via compudynamics
|
||
per node."
|
||
|
||
The second half is recorded as said and is not yet designed. It bears on
|
||
the open question of section 1e — what moves control between nodes, and
|
||
when — which `ENGINE.md` leaves to step 6.
|
||
|
||
## 1j. Where a v4 word's accounts live (ruled 2026-10-05)
|
||
|
||
**Found:** `struct VM` (`v3/include/vm.h`) is both the record the kernel
|
||
keeps for a VM and v3's engine state. The kernel and the physics code work
|
||
directly on dictionary records: about 30 source files reach into a
|
||
`DictEntry`'s fields, several hundred times. The heartbeat's decay walks
|
||
the record list by `link` and resumes by `word_id`; parity hashes it; the
|
||
kernel pins `BIRTH` by setting two of its fields. A v3 word's accounts are
|
||
that C record: `execution_heat`, `physics`, the four ACL fields, the
|
||
transition metrics, the word ID. A v4 word has no such record.
|
||
|
||
**Put to Captain Bob:**
|
||
A — the code is the node's and the accounts are the kernel's, joined by
|
||
word ID: for each word on a node the kernel keeps v3's own `DictEntry`,
|
||
every account field as it is; the node tells the kernel when a word is
|
||
defined or forgotten; at each `call` the kernel does on the record what
|
||
v3's inner loop does; v3's physics, heartbeat, ACL words, Stadium word
|
||
layer and parity run on the records unchanged.
|
||
B — the accounts go into each entry's header in the node's memory, and
|
||
the 30 files change to reach them through accessors.
|
||
|
||
**Ruling:** "A is good."
|
||
|
||
Noted with it: on the FPGA a node's counters are meant to be in the fabric,
|
||
not in a kernel record. A is for v4 = v3 now.
|
||
|
||
## 2. Row 3: physics and per-word heat
|
||
|
||
### 2.1 What v3 does
|
||
|
||
**For every word a definition executes** — the inner loop,
|
||
`kernel/src/vm/vm_core.c:757-880`, and the same on the interpreter's path at
|
||
`:1053-1110`:
|
||
|
||
1. **Decay (Loop #3).** The word's heat is reduced by the ticks since it was
|
||
last touched times the decay slope, in Q48.16
|
||
(`physics_metadata_apply_linear_decay`, `v3/src/physics_metadata.c`). Not
|
||
for a frozen word, and not while L8 has gated Loop #3 off.
|
||
2. **Heat (Loop #1).** The word's `execution_heat` is raised by one.
|
||
3. **Stadium.** The word's ID, the VM's ID and the tick go to the Stadium's
|
||
conserved heat wire (`stadium_word_dispatch`).
|
||
4. **Rolling window (Loop #2).** The word's ID is recorded
|
||
(`rolling_window_record_execution`).
|
||
5. **Pipelining (Loop #4).** The transition from the previous word is
|
||
recorded, the most likely next word is worked out, and a likely one is
|
||
promoted to the hot-words cache.
|
||
6. **ACL.** The word's TTL is counted down, or the word rechecked at zero; a
|
||
denied word stops the definition.
|
||
7. The word runs. Then its physics record is touched (temperature, time) and
|
||
the heartbeat's count of words executed is raised.
|
||
|
||
**On every heartbeat tick** — `kernel/src/vm/vm_runtime.c`, driven by the
|
||
timer interrupt: `vm_tick`; the window tuner (Loop #5, `:158`); the slope
|
||
validator (Loop #6, `:228`); background decay (`:355`); the L8 mode update
|
||
(`:400`), which turns loops on and off; the inference engine (`:579`); and a
|
||
published snapshot.
|
||
|
||
**What each word carries** — `DictEntry`, `v3/include/vm.h:342-358`:
|
||
`execution_heat`; a `DictPhysics` record (temperature, last decay tick, mass,
|
||
state flags); a pointer to its transition metrics; a stable `word_id`; the
|
||
four ACL fields; and the `WORD_FROZEN` and `WORD_PINNED` flags.
|
||
|
||
All of this is running before the first prompt. POST itself executes under
|
||
it.
|
||
|
||
### 2.2 What v4 has
|
||
|
||
- A v4 word is native code. Executing one is the `call` opcode going to its
|
||
address. There is no inner loop, so there is no place in software where
|
||
v3's seven steps could happen.
|
||
- Short words are not called at all. `DUP`, `+`, `@`, `>R` and about fifty
|
||
others are compiled in line as opcodes (`header DUP inline`,
|
||
`v4/capsule/forth.v4`). At run time nothing says "this was `DUP`".
|
||
- The engine counts three things as instructions retire (`v4/src/exec.c`):
|
||
each opcode's count (32 counters), the anti-clock, and a count for each
|
||
call target.
|
||
- **The call-target count is a stand-in, by its own account.** It is a C
|
||
array beside the node, 4096 entries, written 2026-10-02
|
||
(`v4/src/heat.c`). A call to an address of 4096 or above is silently not
|
||
counted. The nucleus runs to about word 6300 and capsule definitions start
|
||
at 8192, so some assembled words and every capsule word get nothing. No
|
||
FORTH word can read it: `ACL-HEAT@` returns 0 (`v4/capsule/acl.v4:80`).
|
||
- A dictionary entry is three cells before the code — flags, name, link
|
||
(`v4/capsule/dict.v4`). It has no heat, no physics record, no word ID.
|
||
- There is no decay, rolling window, pipelining, inference, L8, heartbeat
|
||
or Stadium wire on the node.
|
||
|
||
### 2.3 What the v4 design documents say
|
||
|
||
The documents ruled on 2026-10-01 do not say "as v3". They say:
|
||
|
||
- `JUSTIFICATION.md` section 4, item 4: "Compudynamics as a side effect of
|
||
execution. Heat counters, the anti-clock, and the heartbeat are driven by
|
||
instruction retirement in hardware. They cost no instructions."
|
||
- `DECOMPOSITION.md` section 1.4: "every `call` increments the heat counter
|
||
of its target".
|
||
- `DECOMPOSITION.md` section 7: registers `HEAT-OP[0..31]`,
|
||
`HEAT-CALL[...]`, `ANTICLOCK`, `HEARTBEAT`, `GOV-*`.
|
||
- `DECOMPOSITION.md` sections 5.19 and 5.23 use the words "heat table" for
|
||
`HEAT-CALL` (`ENTROPY@`, `FREEZE-WORD`).
|
||
- D-6, **deferred**: "Which heat structures exist in hardware: per-opcode
|
||
counters only, or also per-call-target and word-to-word transition
|
||
counters. … The golden model implements per-opcode and per-call-target
|
||
heat in the meantime."
|
||
- Section 5.22: the six `PIPELINING-*` words are retired, to return "if D-6
|
||
adds transition counters". Section 5.21: the hot-words cache words are
|
||
retired as having no equivalent on a node.
|
||
|
||
So the 4096-entry array is the "in the meantime" of D-6, and "heat table"
|
||
is these documents' own term.
|
||
|
||
### 2.4 The conflict
|
||
|
||
Today's ruling and those documents do not say the same thing, in three
|
||
places. Each needs a ruling; none is mine to settle.
|
||
|
||
**A. Where a word's heat is kept.** v3: in the word's own dictionary entry.
|
||
The documents: in the node, as a register indexed by call target.
|
||
|
||
**B. What counts as a word.** v3 counts every execution of `DUP` as
|
||
`DUP`'s. v4 compiles `DUP` in line; what is counted is the `dup` opcode,
|
||
and a word built of several opcodes in line (`2DUP` is `over over`) leaves
|
||
no count of its own. v3's per-word heat for these words cannot be had from
|
||
a v4 node as it compiles now.
|
||
|
||
**C. Which loops exist.** v3 runs all seven and L8 before the prompt. The
|
||
documents retire Loop #4 (pipelining) and the hot-words cache, and leave
|
||
the rest to registers whose behaviour is not written down beyond their
|
||
names.
|
||
|
||
### 2.5 What each answer would take
|
||
|
||
Offered so that the ruling can be made knowing its cost. Not a plan.
|
||
|
||
**If heat is kept as v3 keeps it (A, v3's way).** Each entry gains cells
|
||
before its code, beside flags, name and link: heat, last-decay tick, word
|
||
ID, and what of `DictPhysics` is kept. `dict.v4`'s entry words, the header
|
||
macro of the text assembler and every assembled `header` line account for
|
||
them. The node, on `call`, finds the entry from the target address and does
|
||
steps 1 to 5 of section 2.1. Heat is then ordinary memory: `HEAT@` is a
|
||
fetch, and it is inside the dictionary, so the dictionary hash either
|
||
covers it or is defined to skip it, as v3's canonical hash skips it.
|
||
|
||
**If heat is kept as the documents have it (A, the node's way).** The
|
||
call-target counts become part of the node, cover every address of it, and
|
||
are readable through `HEAT-CALL` as a memory-mapped register, which needs
|
||
D-4, the memory map, settled. This is not "as v3" in where heat lives; it
|
||
is in what is counted.
|
||
|
||
**B either way.** Three ways, and they are not equivalent:
|
||
|
||
1. In-line words are counted by opcode: `DUP`'s heat is `HEAT-OP[dup]`.
|
||
Words that are more than one opcode in line have no heat. Not as v3.
|
||
2. Nothing is compiled in line except what must be (`>R`, `R>`, `R@`, `I`,
|
||
`J`, `LEAVE`: a call would bury the return address). Every other word is
|
||
called and so counted. As v3 for those words; slower, and the six that
|
||
must stay in line are still uncounted.
|
||
3. The compiler lays down a marker the node counts and does not execute.
|
||
That is a new opcode or a new use of one, which is a change to the
|
||
instruction set.
|
||
|
||
**C.** For each of Loops #2 to #7 and L8: where its state lives, what
|
||
advances it, and what the heartbeat is on a node that has no timer of its
|
||
own (v3's is the kernel's timer interrupt; the documents' is "in the
|
||
fabric"). v3's code for each is the reference: `rolling_window_of_truth.c`,
|
||
`physics_pipelining_metrics.c`, `inference_engine.c`, `ssm_jacquard.c`,
|
||
`vm_runtime.c`.
|
||
|
||
### 2.6 What depends on this
|
||
|
||
- **Decomposition.** Moving a word from assembler to a colon definition
|
||
does not change how it is called, but it turns in-line opcodes into
|
||
calls, which changes what is counted under every answer to B. It should
|
||
wait for B.
|
||
- **ACL.** v3's TTL comes from heat (`ACL-TTL-COMPUTE`). With `ACL-HEAT@`
|
||
returning 0 every word has the same TTL. Row 8 waits for A.
|
||
- **POST's other 500 cases** (row 4) include the physics-freeze and
|
||
inference modules, which test words that read and write heat.
|
||
- **K.** Nothing computes K on v4. It is a figure over heat; it waits for A
|
||
and C.
|
||
|
||
### 2.7 Ruling (Captain Bob, 2026-10-05)
|
||
|
||
> One more time: v4 = v3 functionally. We will use [heat accumulation] on
|
||
> 32 opcodes rather than words. Before, a word was our smallest unit; now
|
||
> it is the opcode.
|
||
|
||
This settles section 2.4:
|
||
|
||
- **A.** Heat is kept per opcode: 32 accumulators in the node. Not in a
|
||
word's dictionary entry, and not per call target.
|
||
- **B.** The unit is the opcode. Whether a word is called or compiled in
|
||
line makes no difference to what is counted. `call` is one of the 32 and
|
||
is counted as itself.
|
||
- **C.** v4 = v3 functionally, so every loop v3 runs before its prompt
|
||
runs on v4, over opcodes where v3's runs over words.
|
||
|
||
What follows from it, and is not yet done:
|
||
|
||
- The per-call-target array (`v4/src/heat.c`, `call[]`, the freeze mask,
|
||
`v4_heat_on_call`) has no place and is to be removed, with `HEAT-CALL`
|
||
in `DECOMPOSITION.md` sections 1.4, 5.19, 5.23 and 7. D-6 is answered:
|
||
per-opcode.
|
||
- Sections 5.21 and 5.22 of `DECOMPOSITION.md` retire pipelining and the
|
||
hot-words cache. Under C they are not retired.
|
||
- Decomposition no longer waits on this: moving a word from assembler to a
|
||
colon definition changes which opcodes run, and those are counted
|
||
wherever the code is.
|
||
|
||
Still open, for the words v3 keys by word and v4 must key by something:
|
||
|
||
- **ACL.** Access control is per word, and v3 works a word's TTL out from
|
||
that word's heat (`ACL-TTL-COMPUTE`). A v4 word has no heat of its own.
|
||
- **Freeze and pin.** v3 freezes or pins a word's heat (`FREEZE-WORD`,
|
||
`WORD_PINNED`). In v4 what is frozen is an opcode, or nothing.
|
||
- **Hot-words cache.** v3 promotes a hot word so that it is found faster.
|
||
A v4 opcode is not looked up.
|
||
- **Heartbeat.** What ticks it on a node (v3: the kernel's timer
|
||
interrupt).
|
||
|
||
## 3. Stand-ins in v4, as found 2026-10-05
|
||
|
||
Each describes itself as one. None has been changed.
|
||
|
||
| What | Where | Says of itself |
|
||
|---|---|---|
|
||
| Per-call-target heat | `v4/src/heat.c`, `v4/include/v4/heat.h` | "in the meantime" (D-6) |
|
||
| `ACL-HEAT@` | `v4/capsule/acl.v4:80` | returns 0; "not readable from a programme yet" |
|
||
| Console | `v4/include/v4/node.h:136`, `:156` | "the model stands in for the console" until the mesh exists |
|
||
| Block storage | `v4/include/v4/node.h:248`, D-19 | "the model stands in" until the mesh carries the storage service |
|
||
| Memory map | `v4/tests/host_map.h`, D-4 deferred | a test header the product image is built from |
|
||
| CA public key | `v4/capsule/ACL.fth:40` | "placeholders, as in v3" |
|
||
| Address-dependent POST cases | `v4/tools/post79_rules.py`, `ADDRESS` | check how many values are left, not which |
|
||
|
||
`NUCLEUS.md` section 3 puts Q48, logging, ACL and the StarForth extensions
|
||
out of scope until FORTH-79 is accepted. Under the ruling at the head of
|
||
this file they are in scope, since v3 has them before its prompt. That
|
||
section is to be corrected once the rows above are ruled.
|