Files
LithosAnanake/docs/v4.0.0/ENGINE.md
T
rajamesandClaude Opus 5.5 18adf59091 fix(v4.0.0): POST leaves nothing, as v3 really does; and what the review of step 6b found
Captain Bob was told that v3 leaves the words POST's cases define in the
dictionary, and ruled that v4 should.  That was false: v3's run_test_suite
puts the dictionary back after each word's cases (test_common.c:333,
:365).  Shown that, he ruled that POST leaves nothing.  The boot now
seals the system, runs POST, and has the node do COLD, whose printing is
not shown; PARITY:V4_SYSTEM is the system as sealed.

A case the node does not come back from ends POST there, named, with how
many were not run: it would have stalled the boot for hours, where the
capsule had ended it.  The runner's test of it now uses a word that
really never ends.

hosted-check also requires that RS1 and T{ are unknown after boot, and
boots a program whose POST has failing cases (tests/post_cases_fail.c):
PARITY:FAIL, POST: FAILED, no prompt, none of a case's printing shown.

Comments and documents that still named the POST capsule or its two
hooks are brought up to date; NUCLEUS.md 6.3 says what v3 does, with the
lines, and how the wrong ruling came about.

make -C v4 test, sanitize and hosted-check pass; amd64, aarch64 and
riscv64 boot, POST 538 of 538, word_count=314, dict_hash
0x220ab283a504a3b3 on all six: logs/20261007-112638, -112901, -113220.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 11:40:20 -04:00

435 lines
23 KiB
Markdown

# StarForth v4.0.0 — The F18 engine in the VM's place
Design, 2026-10-05. Written from Captain Bob's rulings of that day
(`V3-PARITY.md` sections 1a to 1h, where each is recorded with its words)
and from v3's code. It replaces the lone-node assumptions of `NUCLEUS.md`
sections 5.3 and 7 to 9.
## 1. The principle
`JUSTIFICATION.md` section 16: v4 is equivalent to v3 at any point in time;
"the only difference is the machine underneath: the F18-derived engine
instead of the original StarForth VM."
Ruled 2026-10-05: v4 is exactly like v3 in functional requirements up to
the first FORTH prompt, and that is the stopping point for now. Nothing is
stubbed, simulated or stood in for.
So v4 is not a new system beside LithosAnanke. It is LithosAnanke with a
different machine executing its FORTH.
## 2. What stays, and what is replaced
**Stays, untouched:** everything the kernel does. The Stadium and each
patron's own accounts; sessions; the switcher; kernel-Hermes; the capsule
directory, birth protocol, hashing, signing and parity; the block
subsystem and its devices; identity and Zuse; the HAL console, Hestia, the
console proxies and the kernel's REPL; the heartbeat; the boot order in
`kernel_main.c`.
**Replaced:** the part of a v3 VM that executes FORTH.
| v3 | v4 |
|---|---|
| The inner interpreter (`v3/src/vm.c`, `kernel/src/vm/vm_core.c`) | The node: `v4/src`, 32 opcodes |
| The dictionary as a list of C `DictEntry` records | The dictionary in the node's memory (`v4/capsule/dict.v4`) |
| The FORTH-79 words as C functions (`v3/src/word_source`) | The assembled nucleus (`v4/capsule/*.v4`) and colon definitions in `capsules/v4/forth79.4th` |
| The outer interpreter and compiler in C | The same, in the nucleus |
A v3 VM is also a record the kernel keeps: its identity, its Stadium ID,
its heartbeat state, its place in the registry, its Zuse flags. That
record stays. What changes is what stands behind it.
## 3. The interface
The kernel reaches a VM through a small number of things. Each is listed
with what v3 has, what was ruled, and what a node needs. This is the whole
of the work: when a node answers all of these, the kernel's boot runs on
it as it runs on a v3 VM.
### 3.1 Interpret this text
*v3:* `vm_interpret(vm, text)`. The kernel's REPL hands over each line it
has read; `capsule_exec_payload` hands over each line of a capsule;
kernel-Hermes hands over a message's payload when the target drains its
queue. The VM runs the text and returns, with its error flag set or not.
*Ruled:* the kernel hands a node a whole line and takes characters back;
the node's own prompt loop plays no part at that level. A node receives a
message by being handed its text.
*v4:* the node has an entry that interprets the text in its input buffer
and then stops, saying how the line ended: completed, ended in an error,
or `QUIT`. It prints no prompt and no ` ok`. The buffer takes 1024
characters, a block, as v3's does. `QUIT` and `ABORT` end the line and
return to whoever handed it over. `KEY`, `EXPECT` and `QUERY` stay
FORTH-79 words that read characters.
With this one entry v3's capsule loader, REPL and message drain can all
drive a node, and `v4/system/boot.c`'s own loader is not needed.
### 3.2 A character out
*v3:* the host service `putc`, then `console_putc`; the fabric adds
`[user@VM]`.
*v4:* the node's `EMIT` gives the character to its host, which calls the
same service. Present today.
### 3.3 Asking the kernel
*v3:* a kernel word is a C function registered in the VM's dictionary:
`BIRTH`, `KILL`, `USE`, the block words, the Stadium and Hermes words, the
framebuffer words. It takes its arguments from the VM's data stack.
*Ruled:* a node asks for a block by number and the kernel decides the
rest; a node sends a message by asking.
*v4, ruled 2026-10-05:* a kernel word is an ordinary dictionary entry on
the node whose body writes its request number to the node's port. The
write blocks the node until the kernel, its neighbour on that port, has
served it (`DECOMPOSITION.md` section 6: "a write blocks until the
neighbour reads"). The kernel serves between the node's opcodes, so the
node is always stopped when C touches it; the function takes its arguments
from the node's data stack and leaves its results there. Kernel words are
made by handing the node text at boot. The C functions are v3's.
### 3.4 The stacks and the dictionary, from the kernel's side
*v3:* `vm_push`, `vm_pop`, `vm_find_word`, and the fields of a
`DictEntry` (the kernel pins `BIRTH` and `CAPSULE-BIRTH` by setting two of
them).
*v4:* the same operations on the node's stacks and on entries in the
node's memory.
### 3.5 A word is being executed
*v3:* the inner loop, for every word: the ACL's countdown and recheck,
`execution_heat`, `stadium_word_dispatch`, the rolling window, pipelining.
*Ruled:* the word card stays exactly as v3 — a run-time check, the TTL
adaptive from the word's own count. Every kind of patron keeps its own
accounts; the word's are as v3. Opcodes are counted and left unwired.
*v4:* the node dispatches a word at the `call` opcode, one place in the
engine. The node does there what v3's loop does. A word that is to be
checked must be called, so the in-line words that can be called become
calls.
### 3.6 An error
*v3:* `vm->error`. *v4:* how a line ended (3.1), and the node's error
register for a C function to raise one.
### 3.7 A tick
*v3:* the kernel's timer drives `vm_tick` and the heartbeat state in the
VM's record. *v4:* unchanged; that state is the record's, not the
engine's.
### 3.8 The dictionary hash
*v3:* `vm_dict_hash_fn`, a hook the birth protocol calls. *v4:* the same
hook, answered from the node's dictionary.
## 3b. A word's two halves (ruled 2026-10-05: "A is good")
`V3-PARITY.md` section 1j. A word on a node is its name and its code, in
the node's memory, with its word ID in its header. Its accounts are v3's
own `DictEntry` record, kept by the kernel: `execution_heat`, `physics`,
the four ACL fields, the transition metrics, the word ID. The two are
joined by the word ID.
- The node tells the kernel when a word is defined and when words are
forgotten, by a request through its port. The kernel makes or drops the
record.
- At each `call` the kernel does on the record what v3's inner loop does
(3.5).
- v3's physics, heartbeat, ACL words, Stadium word layer and parity run on
the records, unchanged. The ACL fields v4 keeps in an entry's flags cell
(`v4/capsule/acl.v4`) go: v3's C ACL words serve a node as they are.
## 3c. What exactly is swapped, in v3's own files
Measured 2026-10-05. Each build already has one file that is the
interpreter, and the rest of v3 calls into it:
| Build | The interpreter file | The stacks |
|---|---|---|
| Hosted | `v3/src/vm.c` | `v3/src/stack_management.c` |
| Kernel | `kernel/src/vm/vm_core.c` (the kernel build leaves `v3/src/vm.c` out) | the same |
The functions in it that execute FORTH, and so are the node's to answer:
`vm_interpret`, `vm_interpret_word`, `execute_colon_word`, `acl_recheck`,
`vm_parse_word`, `vm_parse_number`, `vm_enter_compile_mode`,
`vm_exit_compile_mode`, `vm_compile_word`, `vm_compile_literal`,
`vm_compile_call`, `vm_compile_exit`, `vm_make_immediate`; the memory
accessors `vm_addr_ok`, `vm_ptr`, `vm_load_u8`, `vm_store_u8`,
`vm_load_cell`, `vm_store_cell`; and the stacks, `vm_push`, `vm_pop`,
`vm_rpush`, `vm_rpop`. The rest of those files — host services, the time
base, `vm_cleanup` — is not the engine and stays.
So the swap is a third interpreter file, for both builds: the same
functions, answered by a node. Everything else of v3 is compiled as it is.
**The two products part here (Captain Bob, 2026-10-05):** "I would say we
are at a fair point where the hosted product and the bare metal product
diverge completely."
This section first said the hosted v4 product should become v3's hosted
program with the node as its interpreter. That is withdrawn. From here:
- **The bare-metal product** is LithosAnanke with the node in the VM's
place. Everything in this document about the kernel's interface, word
records, the hook at `call`, the fleet and identity is the bare-metal
product's. The third interpreter file is for the kernel build.
- **The hosted product** is its own thing and is not required to follow
the bare-metal one. Today it is the node, the nucleus, the FORTH-79
capsule, POST and its own prompt (`v4/tools/hosted.c`,
`v4/system/boot.c`), and it works on three ISAs.
- **The six builds no longer have to print the same lines.** The three
hosted builds agree with each other; the three bare-metal builds agree
with each other.
**What the two share (confirmed by Captain Bob the same day, "yes,
exactly"):** the engine in `v4/src`; the FORTH-79 capsule,
`capsules/v4/forth79.4th`; and its POST (then `capsules/v4/post79.4th`; since `MESH.md` step 6b, 2026-10-07, the kernel's cases, `v4/system/post_cases.c`). **The
nuclei are separate.** The bare-metal nucleus needs word IDs in its
headers and words that are called where the hosted one compiles them in
line; the hosted nucleus need not have either. The hosted product may
become a hosted version of an SDK.
**Two things in v3's C words do not carry over as they are**, and must be
dealt with word by word:
- A C word that takes an address from the stack and reads memory through
`vm_ptr` gets a pointer into v3's flat bytes. A node's bytes are four to
a cell (D-1), so they are not a flat run of C bytes. Such a word needs
the text copied out of the node. `mama_forth_words.c` has 40 such uses.
- A C word that reaches into `vm->data_stack` or `vm->dsp` directly must
go through `vm_push`, `vm_pop` or a depth accessor. `mama_forth_words.c`
has 45.
Both are changes to files v3 also builds, so each must leave v3 as it is
and be accepted by v3's own three-ISA boot.
## 3a. Many VMs at once (Captain Bob, 2026-10-05)
> Do not forget that this is multiuser, multitasking, and a hybrid of
> preemptive and cooperative.
And, the same day: "Hera will be the process manager via compudynamics per
node."
What that asks of the engine, and what it has:
- **A node can be stopped between any two instruction words and gone on
with later.** Everything a node is doing is in the node and its
execution state; `v4_exec_step_word` runs one instruction word and
returns. So whoever runs the nodes can take the processor from one at any
word and give it to another: that is the preemptive half, and the engine
already allows it. Nothing may be built that needs a node to run a line,
or a request, to its end without interruption.
- **A node gives way by itself when it writes to its port.** It is blocked
until served (3.3), and while it is blocked another can run: that is the
cooperative half.
- **Each node has its own execution state.** The place a blocked node goes
on from is kept per node (`v4_exec_state`), never in one shared place.
- **Each user is a VM** (`FABRIC-2.md` D.2: "a session IS a VM"), so each is
a node, with its own dictionary, stacks and ACL cards.
What is not designed: who decides which node runs next, and when. In v3
that is the switcher, reading what kernel-Hermes publishes, at the
checkpoint in the inner loop; the ruling makes it Hera's, by compudynamics.
It is step 6's, and section 6 lists it as open.
**The loop that runs one node until its line ends** (`v4_boot_line`,
`v4/system/boot.c`) is the lone node's and the hosted program's, where
there is one node and nothing to share the processor with. It is not how
the kernel will run a fleet, and goes with the lone node at step 5.
## 3d. Where v4 is going, and the next step (Captain Bob, 2026-10-05)
> The entire point is that ultimately we have F18 engines digesting
> capsules alone, and in a sense can be anything written in F18 assembler
> for our fabric — 144 someday as a 12x12 grid, but I want a 12^3 FPGA
> ultimately, where using a capsule digester like StarForth, it's more
> than an operating system.
So the unit is an engine and the capsule it digests; StarForth is one
digester. The engine in `v4/src` must stay free of anything specific to
StarForth or to the kernel, and what is built to make v4 equal v3 on a PC
(section 3b's records, v3's C functions serving requests) stays on the
kernel's side. (12^3 is a three-dimensional grid: six neighbours.
`DECOMPOSITION.md` section 6 has four ports. Noted, not ruled.)
Asked whether v4 = v3 on bare metal comes first, or nodes talking to
nodes: **"The next step is talking nodes sharing the common SSD, and [they]
may or may not have block storage available."** Step 4 below waits.
Rulings on that step so far:
- **The ports are the transport; the message is what is transported.**
Node to node, a write blocks until the neighbour reads
(`DECOMPOSITION.md` section 6). What travels is v3's Hermes message with
what it carries — type, from, to, channel, heat and TTL, ACL tag, a
payload of FORTH text up to a block — so v3's messaging rules are not
dropped; they go with the message.
- **Storage: some nodes have storage of their own** (a thumbdrive, as a
user's identity has today), in addition to or instead of the common SSD.
The common SSD is the system-resident store, Artemis's disk
(`FABRIC-2.md` F.16).
- **The first set of nodes: "2x2 + 1 central".** Five: four in a 2x2,
and one in the middle. Read back to Captain Bob, and not corrected, as:
each outer node wired to its two grid neighbours and to the centre; the
centre wired to all four; the centre is Hera.
- **A node has six ports, not four** — "A, as long as it can scale at
runtime adaptively." With four, the centre's are all taken by the outer
nodes and nothing is left for the common SSD or the console. Six is also
what a 12^3 grid needs. `DECOMPOSITION.md` section 6 is to be corrected.
- **It must scale at run time, adaptively.** The number of nodes and their
wiring are not fixed when the system is built.
- **The geometry is not fixed: "Not constrained by a 3D world. Other
geometries might be better."** Said when asked what a centre's two
remaining ports of six were for. So six, which came from a 12^3 grid's
six neighbours, is not a given either; nor is any one shape.
- **The geometry is data, not design (confirmed, "yes").** A node has a
number of ports, and that number is a parameter, as cell width and node
memory are. Which port connects to what is a table that can change while
the system runs. A geometry is a rule for filling in that table; the
units-and-centres rule below is the first, not the only one. Devices —
the common SSD, the console, a node's own drive — are things on the
other end of a port, in the same table. Nothing in the engine knows
which geometry is in use. A node is told, or finds out, which port leads
toward a destination.
- **What Hera's process management decides (ruled, "A"): which nodes
exist and are awake, not whose turn it is.** Every node that is not
blocked runs; on one processor that is each in rotation, an instruction
word at a time, which is what "all at once" is there and is not a
policy. From each node's heat Hera decides which are born, put to sleep,
woken and killed (`JUSTIFICATION.md` sections 6 and 7). Cooperative is a
node blocking itself at a port; preemptive is Hera putting a node to
sleep or killing it between any two instruction words. Nothing in this
depends on there being one processor. With it: an idle node does not
spin; it is blocked reading its ports and wakes when a neighbour writes,
and the console handing it a line is a message arriving on a port like
any other.
- **A node is born empty (ruled, "A").** It has nothing but the ability
to listen: it is blocked reading its ports, and the first thing a
neighbour sends it is a capsule of F18 code, which it takes in and runs,
as an F18 node executes what arrives at its port. StarForth's nucleus is
then itself a capsule — the one that makes an empty node able to digest
FORTH-source capsules — and is named, hashed, signed and recorded in
parity like any other, where today it is linked into the binary. The one
thing every node has, whatever it becomes, is the fixed behaviour at
reset by which it takes in its first capsule.
- **Where it is built and proven (ruled, "A"): in the shared engine,
hosted first, then bare metal.** Ports, wiring, nodes born empty and
messages are the engine's and belong to both products. Hosted, on three
ISAs, with a disk image as the common SSD; then the same nodes on bare
metal with the real console, SSD and thumbdrives.
- **How it grows: "Only the central node can connect to only another
central node."** So the five are a unit: four outer nodes and their
centre. An outer node is wired only inside its own unit. Units are joined
centre to centre, and the system grows by units.
The design of this step is being worked out by question and answer and is
not written yet. Nothing of it is built.
## 4. What was built on the detour
| Built 2026-10-05 | What becomes of it |
|---|---|
| `kernel_main.c` calling `sk_v4_run()` before the fleet tables | Goes at step 4. Until then it is how the bare-metal build is kept booting while the interface is built. |
| `v4/system/boot.c`: its own capsule loader and `PARITY:V4_*` lines | Stays for the hosted product. On bare metal v3's birth protocol loads the capsules and prints v3's parity lines (step 4). |
| The node's prompt loop, and the code that strips ` ok` from its output | Goes at step 1. |
| `capsules/v4/forth79.4th`, `post79.4th`, `mkpost.py`, the rules | Stay. POST is the gate for every word moved out of the nucleus. |
| `(CATCH)`, `(EMIT-HOOK)`, `NODE-ERROR` by name | `NODE-ERROR` stays. `(CATCH)` and `(EMIT-HOOK)` went with the POST capsule, `MESH.md` step 6b, 2026-10-07: POST is the kernel's and needs neither. |
| The per-call-target count (`v4/src/heat.c`, `call[]`) | Goes at step 3, when the node does at `call` what v3 does. |
| The hosted Linux product (`v4/tools/hosted.c`, `v4/system/boot.c`) | Stays, as its own product (3c). |
## 5. Steps
Each ends with all six builds booting and agreeing, and is committed with
its logs.
1. **Interpret this text (3.1).** The node's line entry; no prompt loop.
The hosts print the prompt and ` ok`, as v3's REPL does.
**Done 2026-10-05.** `(LINE)`, `(IDLE)`, `(DONE)` and `(LINE-STATUS)` in
`v4/capsule/quit.v4`; `v4_line_*` in `v4/src/image.c`; `v4_boot_line` in
`v4/system/boot.c`. All v4 tests pass at both widths; the six builds
agree; lines typed at the three bare-metal prompts are answered.
`logs/20261005-180922`, `-181152`, `-181541`.
2. **Asking the kernel, and the stacks from the kernel's side (3.3,
3.4).** Settle how a request is carried. Kernel words callable from a
node.
**The carrier is done, 2026-10-05; the rest is not.** Ruled: a node asks
by a blocking write to a port. Built: the port (`v4/src/node.c`,
`v4_node_port_attach`, `v4_node_port_served`); a blocked node goes on
from the opcode after the store, in the same instruction word
(`v4/src/exec.c`); `KERNEL-WORD` (`v4/capsule/compile.v4`), which makes a
word whose body writes its request number to the port; the boot makes
the kernel's words by handing the node text, and serves requests
(`v4/system/boot.c`). A request no one serves is error 12 on the node.
The one kernel word so far is `BYE`, on both products: hosted it leaves
the program, as hosted v3; on the lone node it is v3's cold restart.
`v4/tests/test_port.c`; six builds agree; `BYE` and an unserved request
typed at each bare-metal prompt. `logs/20261005-185506`, `-185734`,
`-190101`.
**The node tells its kernel of its words, 2026-10-05** (3b). An entry
is made in one place in the nucleus and entries go in two; each now
tells through a variable holding the xt of a word to run:
`(WORD-DEFINED)` ( xt -- ) from `(HEADER)`, `(WORD-FORGOTTEN)` ( w -- )
from `FORGET` and `COLD` (`v4/capsule/dict.v4`, `system.v4`). With 0
there, as on the hosted product, no one is told. `test_host_quit.c` is a
kernel that keeps the list and checks it is exactly the node's
dictionary after definitions, a vocabulary, an abandoned definition,
`FORGET`, a refused `FORGET` and `COLD`.
**Found and fixed with it:** since the capsules moved from build time to
boot time, what `COLD` returns to and `FORGET` protects was still the
nucleus alone, so `COLD` lost `U*`, `U/MOD` and `BYE`. The boot now
seals the system when it has loaded it (`v4_image_seal`); `hosted-check`
checks it. `logs/20261005-193045`, `-193307`, `-193636`.
**Not done:** v3's own C functions serving a node. They take a `VM *`
and use `vm_push`, `vm_pop` and, in places, the stack's fields directly;
a node has to stand behind that `VM` record first. That, the records
themselves, and the kernel's interpreter file are step 4.
3. **A word is being executed (3.5).** The hook at `call`; the ACL's
countdown and recheck; the word's count; `stadium_word_dispatch`.
In-line words become calls. The per-call-target count goes.
4. **The node as the kernel's interpreter.** The third interpreter file
(3c), for the kernel build. Under `STARFORTH_V4` the kernel's boot
brings the node up where it brings a v3 VM up: `vm_interpret` hands the
node the line; `register_word` makes a kernel word and its record; the
node's own words get records. `sk_v4_run()` goes. v3's POST runs
through it and its failures are the list of what is not yet there.
5. (Folded into 4: the hosted product is no longer part of this path.)
6. **Hera as v3 has her.** `init.4th` runs on the node; v3's whole POST
passes; v3's parity lines.
7. **The fleet.** Hestia and Artemis; message delivery; who runs next.
With Artemis: she is the owner of the disk, who says it may be
formatted, so that the disk is written as well as read (`MESH.md` step
6); and a drive that arrives is registered by her, at the chain's tail.
8. **Identity and the prompt.** Zuse; `[zuse@Hera] ok>`.
With identity: drives that come and go, as v3 has them — the
home-blocks signature, `WIREBIND`, the user's VM born from the drive,
`EJECT`, and a surprise pull (`MESH.md` 8.7, which says why the storage
step that was to build them another way was withdrawn).
Moving words from the assembled nucleus to `forth79.4th` goes on beside
these, a group at a time, with POST after each.
## 6. Open, and where each must be settled
| Open | Before |
|---|---|
| How the node tells a dictionary entry from a bare address at `call` | Step 3 |
| `EXECUTE`, which enters a word by a return | Step 3 |
| `>R R> R@ I J LEAVE`, which cannot be called | Step 3 |
| Who decides which node runs next, and when: Hera, by compudynamics; preemptive and cooperative | Step 7 |
| The node's safe moment for message delivery and switching | Step 7 |
| `PAD 42 OVER !`, a byte address given to `!` (D-1) | Step 6 |
| Which word patrons' accounts the hosted product keeps, having no Stadium | Step 3 |