- capsule/numout.v4: <# # #S HOLD SIGN #>, . .R U. U.R D. D.R, ?, SPACES, DECIMAL HEX OCTAL -- the definitions DECOMPOSITION.md 5.8 gives and the mesh-node tests execute, now words of the host node's vocabulary. - .S, which D-16 makes possible again: as v3, the depth, then every value from the deepest, then a new line. It needs six cells of the stack free. - tests/test_host_quit.c: printed from the prompt, with 14 more sessions that are transcripts of the v3 binary, and the ends of the number range at each cell width. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
101 KiB
StarForth v4.0.0 — Primitive Decomposition
This document assigns every C primitive in StarForth v3 (admin/LithosAnanake at 6302dcb) a fate in
StarForth v4.0.0. v4 is built on a 32-instruction core derived from Chuck Moore's F18 (the GA144 node).
Everything that is not one of those 32 instructions becomes capsule code, a service message to another
node, a memory-mapped register, or is retired.
The rationale is in JUSTIFICATION.md. This document is the specification.
Status. Every colon definition below is written against the ISA in §1 and has been traced by hand, but none has been executed. Each one becomes a POST test target: it is correct when the hosted v4 golden model produces the same results as the v3 C primitive it replaces.
Contents
- Fates
- The v4 core ISA
- Compiler conventions
- Decisions
- Foundation layer (new words)
- Fate of every v3 primitive
- Messaging between nodes
- Memory-mapped registers
0. Fates
| Code | Fate | Meaning |
|---|---|---|
| OP | Instruction | One of the 32 core opcodes. |
| IN | Inline macro | A short opcode sequence the compiler places in-line. Never called. Required for anything that touches the return stack, since a call would bury the return address. |
| CAP | Core capsule | A colon definition in the core capsule, built only from OP, IN, and earlier CAP words. |
| CC | Compiler capsule | Part of the compiler/interpreter capsule, which runs on the host node only. Mesh nodes receive compiled code. |
| DEV | Device service | A message to a device node that owns real hardware (console, storage, display, keyboard, log). |
| HERA | Hera service | A message to the Hera node, which owns lifecycle, identity, capsules, ACLs, and Stadium admission. |
| MM | Memory-mapped | An @ or ! against a hardware register (heat counters, anti-clock, governor, stack pointer). |
| RET | Retired | A diagnostic of v3's software machine that has no equivalent in v4. |
1. The v4 core ISA
1.1 Machine model
| Item | v4 node |
|---|---|
| Cell | 32 bits (mesh node). The host node's width is a build parameter; see D-5. |
| Addressing | Word-addressed (D-1). |
| Registers | T (top of data stack), S (second), R (top of return stack), P (program counter), A and B (address registers). |
| Stacks | F18 hardware stacks, not addressable by code: data stack T, S + 8 (10 deep), return stack R + 8 (9 deep). Each counts what it holds, and a push onto a full stack or a pop from an empty one is a fault (D-16, which replaces D-2's silent wrap-around). |
| Instruction word | Six 5-bit slots, plus 2 spare bits. |
1.2 Instruction word layout
31 27 26 22 21 17 16 12 11 7 6 2 1 0
+--------+--------+--------+--------+--------+--------+----+
| slot 0 | slot 1 | slot 2 | slot 3 | slot 4 | slot 5 | xx |
+--------+--------+--------+--------+--------+--------+----+
Slots execute left to right. A branch (jump, call, next, if, -if) takes its target from all
bits to the right of its slot, and that target replaces the same low bits of P (page-relative, as in
the F18). Branches are therefore legal in slots 0–3 only:
| Branch in slot | Address bits | Reach |
|---|---|---|
| 0 | 27 | whole address space |
| 1 | 22 | 4M words |
| 2 | 17 | 128K words |
| 3 | 12 | 4K words (node-local) |
1.3 The 32 opcodes
Opcode numbering follows the F18. Two names differ from Moore's: F18 - is renamed inv and F18 or
(which is exclusive-or) is renamed xor, so instruction names never collide with FORTH-79 word names.
| Op | Name | Effect |
|---|---|---|
| 00 | ; |
Return: P ← R, pop R. |
| 01 | ex |
Swap P and R (co-routine / execute). |
| 02 | jump a |
P ← a. |
| 03 | call a |
Push P to R, P ← a. |
| 04 | unext |
If R ≠ 0: decrement R, restart the current instruction word at slot 0. Else pop R. |
| 05 | next a |
If R ≠ 0: decrement R, P ← a. Else pop R. |
| 06 | if a |
If T = 0: P ← a. Does not pop T. |
| 07 | -if a |
If T ≥ 0 (sign bit clear): P ← a. Does not pop T. |
| 08 | @p |
Push the word at P, P ← P+1 (literal). |
| 09 | @+ |
Push the word at A, A ← A+1. |
| 0A | @b |
Push the word at B. |
| 0B | @ |
Push the word at A. |
| 0C | !p |
Store T at P, pop, P ← P+1. |
| 0D | !+ |
Store T at A, pop, A ← A+1. |
| 0E | !b |
Store T at B, pop. |
| 0F | ! |
Store T at A, pop. |
| 10 | +* |
Multiply step: if bit 0 of A is set, T ← T+S; then shift T:A right one bit. See D-3. |
| 11 | 2* |
T ← T << 1. |
| 12 | 2/ |
T ← T >> 1, arithmetic. |
| 13 | inv |
T ← ~T. |
| 14 | + |
T ← S + T, pop. |
| 15 | and |
T ← S & T, pop. |
| 16 | xor |
T ← S ^ T, pop. |
| 17 | drop |
Pop T. |
| 18 | dup |
Push a copy of T. |
| 19 | pop |
Pop R onto the data stack. |
| 1A | over |
Push a copy of S. |
| 1B | a |
Push A. |
| 1C | nop |
Nothing. |
| 1D | push |
Pop T onto the return stack. |
| 1E | b! |
B ← T, pop. |
| 1F | a! |
A ← T, pop. |
Note that @ and ! address through A, not through T. The FORTH words @ and ! therefore become
a! @ and a! !.
1.4 Side effects that are not instructions
Execution itself drives the physics. Every retired instruction increments that opcode's heat counter and
advances the anti-clock; every call increments the heat counter of its target. None of this costs an
instruction. Capsule code reads it through the memory-mapped registers in §7.
2. Compiler conventions
Literals. A number in source compiles to @p followed by the value in the next word.
Capsule IF. Native if and -if leave the flag on the stack. The capsule-level IF consumes it,
so the compiler drops it on both paths:
IF body THEN → if L1 drop body jump L2
L1: drop
L2:
IF body1 ELSE body2 THEN → if L1 drop body1 jump L2
L1: drop body2
L2:
BEGIN ... WHILE ... REPEAT and BEGIN ... UNTIL expand the same way.
FOR ... NEXT. n FOR ... NEXT compiles push then the body then next. The body runs n+1
times, as on the F18. FOR ... UNEXT is the same but the body must fit in one instruction word.
Register conventions. A and B are caller-saved. A word that uses them says so. Words in this
document that clobber A: @ ! +! -! 2@ 2! C@ C! D2/ M* FILL ERASE MOVE COUNT CMOVE CMOVE> BLANK -TRAILING COMPARE SEARCH SCAN SKIP DECIMAL HEX OCTAL UM* * UM/MOD Q.FROM-INT Q.TO-INT Q.* Q./ Q.EXP Q.SQRT Q.LOG Q.SIN Q.COS +LOOP J HOLD SIGN # #S . .R U. U.R D. D.R ? DUMP Q.PRINT TYPE SEND RECV. Words that clobber B:
COMPARE SEARCH Q./ Q.EXP Q.SQRT Q.LOG Q.SIN Q.COS <# HOLD SIGN # #S #> . .R U. U.R D. D.R ? DUMP Q.PRINT EMIT CR SPACE SPACES TYPE KEY ?TERMINAL SEND RECV.
Return-stack words (>R R> R@ 2>R 2R> 2R@ I J UNLOOP and the loop runtimes) are always IN.
3. Decisions
Ruled by Captain Bob, 2026-10-01, except D-4 and D-6, which are deferred to development step 2 (the
hosted mesh, JUSTIFICATION.md §10) because only the mesh and the RTL depend on them. Where a
definition below depends on one, it says so.
| ID | Decision | Ruling |
|---|---|---|
| D-1 | Word addressing (pure Moore) or byte addressing. | Word addressing. C@/C! are CAP; CELLS is a no-op. |
| D-2 | Stack depth, and whether the stacks are visible. | F18 circular stacks, hidden. Data stack 10 deep (T, S + 8 circular), return stack 9 deep (R + 8 circular), exactly as the F18. No stack pointer is visible to code, so DEPTH, PICK, ROLL, .S, SP@ and SP! are retired everywhere, host node included. Revised by D-16 (2026-10-04): the sizes stand and the stacks are still not addressable, but they are no longer circular in effect — they are counted and guarded — and DEPTH, PICK and ROLL are back. |
| D-3 | Exact +* semantics at 32 bits: whether the add carries out of T into the shift. |
Plain F18 semantics. The carry out of T is not kept. UM* in §4 is written for this and is exact over the full range (revised and proven on the golden model, 2026-10-02). |
| D-4 | Node memory map, including port and register addresses. | Deferred to step 2. Symbolic names only for now (§6, §7). |
| D-5 | Host node cell width. | Match the host CPU: 32 on a Zynq-7000 (Cortex-A9), 64 on an aarch64 host. The compiler capsule is written width-independent. |
| D-6 | Which heat structures exist in hardware: per-opcode counters only, or also per-call-target and word-to-word transition counters. | Deferred to step 2. The golden model implements per-opcode and per-call-target heat in the meantime. |
| D-7 | ROLL semantics. |
FORTH-79 (2026-10-04, with D-16): n ROLL takes out the n-th value, counting from one and not counting n, and puts it on top; 3 ROLL is ROT, 1 ROLL does nothing; n < 1 is an error. PICK counts the same way: 1 PICK is DUP. v3's count from zero. |
| D-8 | Q48.16 signedness. | Signed. v3's unsigned comparisons and Q.FROM-INT clamping are retired. |
| D-9 | Instruction word width on a 64-bit-cell host (ruled 2026-10-02). | 32 bits at every cell width. Six 5-bit slots plus 2 spare bits, as in §1.2. On a 64-bit host the instruction word is the low 32 bits of the cell and the high half is ignored, so compiled code is identical at both widths. Only data and @p literals are a full cell wide. |
| D-10 | Q48.16 width on a 64-bit-cell node (ruled 2026-10-02). | Two cells at every cell width. A Q value is a signed double on 32- and 64-bit nodes alike, so every Q word is the same double word at both widths. At 32-bit cells this is bit-for-bit v3's 64-bit Q. At 64-bit cells the low cell is v3's value, and where v3 wraps on Q48.16 overflow (a sum past Q max, ABS or NEG of Q min) the high cell carries the true result instead. |
| D-11 | Q./ semantics: division by zero, rounding, overflow (ruled 2026-10-02). |
Saturate and flag; round toward zero; saturate on overflow. The quotient of a * 2^16 / b is rounded toward zero, like SM/REM. When it does not fit a Q value it is clamped to Q max or Q min by its sign. Division by zero returns Q max or Q min by the sign of the dividend (0 / 0 gives 0) and sets the node's NODE-ERROR register (§7), which VM-ERROR? reads. v3 returned 0 on division by zero and saturated whenever the dividend was 2^48 or more, even when the quotient would have fit. |
| D-12 | Q approximations outside their domain (ruled 2026-10-02). | Return 0 and set NODE-ERROR. Q.SQRT of a negative value and Q.LOG of zero or a negative value return 0 and set NODE-ERROR (§7), as Q./ does on division by zero (D-11). v3 returned 0 for ln(0) without a flag and read negative arguments as large unsigned values. |
| D-13 | Pictured-output hold buffer: size and errors (ruled 2026-10-03). | 63 characters, as v3; on error set NODE-ERROR and drop the character. The buffer holds 63 characters at either cell width. HOLD of a value outside 0–255, or into a full buffer, stores nothing and sets NODE-ERROR (§7), which is v3's behaviour (it set its error flag and dropped the character). A full double in base 2 therefore does not fit, as in v3. |
| D-14 | An address outside the node's memory (ruled 2026-10-04). | Guarded: an address fault. Every address a running programme uses is checked before it is used — P when an instruction word or an @p literal is fetched or !p stores, A for @ @+ ! !+, B for @b !b. Outside 0 … memory size − 1 the opcode does nothing (no fetch, no store, A and B untouched), the rest of its instruction word is not executed, both stacks are emptied (D-16), and P becomes the node's fault handler for that kind of fault. The handler does not return to the programme. On the host node the handler is (FAULT) (v4/capsule/quit.v4): it prints Address out of range, ends any definition that was open, prints ERROR and returns to the prompt, from however deep the fault was. A node with no handler stops. v3 printed ERROR alone. What a mesh node's handler does — it has no console — comes with the mesh (step 2). Executed on the golden model (2026-10-04): tests/test_exec.c for every memory opcode and for P, tests/test_host_quit.c from the prompt. |
| D-15 | Division by zero in / MOD /MOD */ */MOD M/MOD (ruled 2026-10-04). |
Guarded: the word reports it and the line ends. Each of these words tests its divisor before anything else. If it is zero the word prints v3's message, its own name and : Division by zero, takes its operands off the stack as v3 does, ends any definition that was open, prints ERROR and returns to the prompt, from however deep; its caller is not returned to. M/MOD says so too, where v3 printed ERROR alone. Source in v4/capsule/forth.v4; executed on the golden model's host node (2026-10-04), including transcripts of the v3 binary. Q./ keeps D-11 (saturate and flag). UM/MOD and SM/REM are internal and unguarded: their callers have checked. What a mesh node, with no console, does on a zero divisor comes with the mesh (step 2); §4's definitions, which the mesh-node tests execute, still leave it unspecified. |
| D-16 | Stack overflow and underflow; DEPTH, PICK, ROLL (ruled 2026-10-04; revises D-2). |
Guarded: a stack fault. Each stack counts what it holds. Before every opcode the node checks that the stacks hold what the opcode takes — including a T or S it only reads — and have room for what it leaves. If not, the opcode does nothing and the node faults exactly as for a bad address (D-14), to that kind's handler: Stack overflow, Stack underflow, Return stack overflow, Return stack underflow, then ERROR and the prompt. Every fault, D-14's included, empties both stacks: the handler does not return, and what a word stopped part-way has left on the data stack is of no use to its caller. (v3 keeps what the failing word had not taken, and names the word: DROP: Stack underflow.) The fault handler is a table of five words, one per kind, each a jump ((FAULTS) in v4/capsule/quit.v4). Two registers (§7) are all a programme sees of the stacks: DSTACK-DEPTH and RSTACK-DEPTH read as the depth, and a store to one empties that stack. There is still no stack pointer and no address for a stack cell, so SP@ and SP! stay retired; .S waits for number output to reach the capsule. The sizes were unchanged by this ruling, 10 and 9 (D-17 then deepens the host node's): one more value, or one more level of call, is now an error message where it used to be silent corruption. Executed on the golden model (2026-10-04): tests/test_exec.c for every opcode at every depth of both stacks, tests/test_host_quit.c from the prompt. |
| D-17 | Stack sizes on the host node (2026-10-04, following D-16). | 32 values and 32 return entries on the host node; a mesh node keeps the F18's 10 and 9. Once the stacks are counted (D-16) their size is a parameter of the node, like its memory, and the host node is the one that runs the interpreter and the compiler underneath the user's programme: at 10 and 9 the prompt left a programme about six values, and / could be used only four words deep. The mechanism is the same at both sizes — top registers over a ring — and so is every word's definition. v3's stacks are deeper still. In the golden model the sizes are V4_DATA_RING and V4_RET_RING (stack.h), set for the host-node tests in v4/Makefile. |
Consequences of D-2 that every definition must respect. The data stack holds 10 items and the
return stack 9, and every call, FOR, DO loop frame and push uses return-stack slots. Nesting
is therefore shallow, and exceeding either depth is a stack fault (D-16; before 2026-10-04 it
corrupted silently). Every CAP
definition in §4 and §5 must be re-checked against these limits before it is accepted as a POST
target; the hand traces so far assumed unbounded stacks.
4. Foundation layer (new words)
These words do not exist as primitives in v3 but everything else is built on them. They are listed in dependency order.
\ ---- helpers ------------------------------------------------------------
: NIP ( a b -- b ) push drop pop ;
: SWAP ( a b -- b a ) over push push drop pop pop ;
: OR ( a b -- a|b ) over inv and xor ;
: NEGATE ( n -- -n ) inv 1 + ;
: ROT ( a b c -- b c a ) push SWAP pop SWAP ;
\ ---- sign and zero tests (raw branches, labels shown) ------------------
: 0< ( n -- flag ) -if L1 drop -1 ; L1: drop 0 ;
: 0= ( n -- flag ) if L1 drop 0 ; L1: drop -1 ;
\ ---- unsigned compare ---------------------------------------------------
: U< ( u1 u2 -- flag ) 2DUP xor 0< IF NIP 0< ELSE - 0< THEN ;
\ ---- multiply (D-3) -----------------------------------------------------
\ Plain F18 +* loses the carry out of T, and its shift keeps T's sign bit, so
\ the loop is exact only while the multiplicand in S and the running T both
\ lie in [-2^(n-2), 2^(n-2)). UM* therefore multiplies by s = u1 2/, which
\ always does, starting T at t0 = u2 2/ when u1 is odd, and gets
\ hi:lo = t0 + s*u2. Then u1*u2 = 2*(hi:lo) + c_lo + c_hi*2^n, where
\ c_lo = u1 & u2 & 1
\ c_hi = (u1<0 ? u2 : 0) + (u1 odd and u2<0 ? 1 : 0)
\ restore the bits the two halvings dropped and the unsigned reading of
\ both top bits. Exact over the full range; proven on the golden model.
: UM* ( u1 u2 -- ulo uhi )
over 1 and inv 1 + over 2/ and \ u1 u2 t0 t0 = u2 2/ if u1 odd
over a! 31 push \ A: u2 R: loop count (FOR's push, early)
push over 2/ pop \ u1 u2 s t0 s = u1 2/
L: +* unext \ u1 u2 s hi A: lo
push drop pop \ u1 u2 hi
2* a -if L0 drop 1 + jump L1 L0: drop L1: push \ R: 2hi + top bit of lo
over -if L2 drop dup jump L3 L2: drop 0 L3: push \ R: + (u1<0 ? u2 : 0)
dup -if L4 drop over 1 and jump L5 L4: drop 0 L5: \ u2<0 ? u1&1 : 0
pop + pop + push \ u1 u2 R: hi'
and 1 and a 2* + pop ; \ lo' hi'
\ u1 and u2 wait on the data stack under the loop (+* touches only T, S and
\ A), so the corrections are made after it, with -if in place of 0< calls,
\ and the return stack holds at most two temporaries. The loop count is
\ pushed before s is made, so the data stack never holds more than four.
\ Leaves its caller 6 data cells under its arguments and 6 return entries
\ (revised 2026-10-02; the first full-range version parked its three
\ corrections on the return stack and left 4).
\ ---- divide: 32-step restoring division, divisor held in A --------------
\ Each step shifts hi:lo left one bit and, when the shifted-out bit of hi was
\ set or hi' >= d, subtracts d from hi' and sets the new low bit of lo. The
\ unsigned compare is done by sign tests in line (as U< does), and x - d is
\ `inv a + inv`, so the loop body makes no calls: it needs one return-stack
\ entry beyond its own count.
: UM/MOD ( ulo uhi ud -- urem uquot )
a! 31 FOR
-if L0 \ hi's top bit set:
2* over -if L1 drop 1 + jump L2 \ hi' = 2hi + top bit of lo
L1: drop L2: push 2* pop \ lo' = 2lo
jump SUB \ true hi' >= 2^n > d
L0:
2* over -if L3 drop 1 + jump L4
L3: drop L4: push 2* pop
dup a xor -if L5 \ top bits of hi' and d differ:
drop -if NOSUB jump SUB \ hi' >= d iff hi' has it
L5: drop dup inv a + inv -if L6 \ same: hi' >= d iff hi'-d >= 0
drop jump NOSUB
L6: push drop pop jump SETBIT
SUB: inv a + inv \ hi' - d
SETBIT: push 1 + pop \ lo' is even: + 1 sets bit 0
NOSUB:
NEXT
over push push drop pop pop ; \ SWAP in line
\ Executed on the golden model (2026-10-02): exact, q*d + r = uhi:ulo with
\ r < d, for every uhi < ud at 32- and 64-bit cells. Outside that range
\ (uhi >= ud, including ud = 0) the result is unspecified, as in FORTH-79; it
\ always terminates after 32 steps. Stack use (D-2): up to 6 data cells may
\ lie under the three arguments and up to 6 return-stack entries under its
\ return address. For UM* the same limits are 6 and 6. This replaces a
\ first version built on U<, 0<, SWAP and OR calls, which was also exact but
\ left only 3 and 3 (too few for SM/REM inside /MOD) and stepped about five
\ times as many instruction words.
\ ---- signed division, truncating toward zero (v3 semantics) -------------
\ The quotient takes the sign of d xor n, the remainder the sign of d. Sign
\ tests are native -if (as in 0<) and NEGATE is in line, so the only calls
\ are DNEGATE and UM/MOD, both call-free inside.
: SM/REM ( d n -- rem quot )
over over xor push \ R: quotient sign (top bit)
over push \ R: + remainder sign (top bit of d)
-if L0 inv 1 + L0: push \ R: + |n|
-if L1 DNEGATE L1: \ |d|
pop UM/MOD \ urem uquot
pop -if L2 drop push inv 1 + pop jump L3 L2: drop L3:
pop -if L4 drop inv 1 + ; L4: drop ;
\ Executed on the golden model (2026-10-02): exact whenever the truncated
\ quotient fits a signed cell, at 32- and 64-bit cells. Stack use (D-2): up
\ to 5 data cells under the three arguments and 3 return-stack entries under
\ its return address; /MOD, one call further out, leaves 2. The first
\ version, built on ABS, DABS, 0< and NEGATE calls, overflowed the return
\ stack inside DABS -> DNEGATE -> D+ -> U> and never returned correctly for
\ a negative dividend.
2DUP, -, and DNEGATE are defined in §5; the compiler resolves forward references within the
core capsule.
5. Fate of every v3 primitive
Section numbers match the v3 primitive reference.
5.1 Stack
| Word | Fate | v4 definition / notes |
|---|---|---|
DROP |
OP | drop |
DUP |
OP | dup |
OVER |
OP | over |
SWAP |
CAP | §4. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 7 data cells and 6 return entries. |
?DUP |
CAP | if Z dup ; Z: ; (dup IF dup THEN without the extra copy). Executed on the golden model (2026-10-03), including a transcript of the v3 binary. |
ROT |
CAP | §4. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 6 data cells and 4 return entries; in line, with SWAP in line, it calls nothing. |
-ROT |
CAP | SWAP push SWAP pop, SWAP in line (ROT ROT in one pass). Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 6 data cells and 5 return entries. |
DEPTH |
CAP | FORTH-79: how many values were on the stack before DEPTH. DSTACK-DEPTH b! @b (D-16). Source in v4/capsule/forth.v4; executed on the golden model's host node (2026-10-04), including a transcript of the v3 binary. |
PICK |
CAP | FORTH-79 (D-7): ( n -- x ), a copy of the n-th value counting from one; 1 PICK is DUP, 2 PICK is OVER. The stack has no addresses, so the n - 1 values above the one wanted are set aside in memory and put back. n < 1 prints PICK: Invalid index; n greater than the depth is a stack underflow fault. It works with the stack full (every cell but one holding a value, and n). v3's PICK counts from zero. Source in v4/capsule/forth.v4; executed on the golden model's host node (2026-10-04). |
ROLL |
CAP | FORTH-79 (D-7): ( n -- ), the n-th value counting from one is taken out and put on top; 3 ROLL is ROT, 1 ROLL does nothing. Built as PICK is, with the same errors. v3's ROLL counts from zero. Source in v4/capsule/forth.v4; executed on the golden model's host node (2026-10-04). |
5.2 Return stack
| Word | Fate | v4 definition |
|---|---|---|
>R |
IN | push — executed on the golden model (2026-10-03) with R@ and R>, including a transcript of the v3 binary. |
R> |
IN | pop — executed on the golden model (2026-10-03), see >R. |
R@ |
IN | pop dup push — executed on the golden model (2026-10-03), see >R. |
5.3 Memory
| Word | Fate | v4 definition / notes |
|---|---|---|
@ |
IN | a! @ — executed on the golden model (2026-10-03), including a transcript of the v3 binary. |
! |
IN | a! ! — executed on the golden model (2026-10-03), including a transcript of the v3 binary. |
+! |
CAP | a! @ + ! — executed on the golden model (2026-10-03), including a transcript of the v3 binary. |
-! |
CAP | a! NEGATE @ + !, NEGATE in line — executed on the golden model (2026-10-03), including a transcript of the v3 binary. |
2@ |
CAP | a! @+ @ (low cell at addr, high at addr+1, as in v3) — executed on the golden model (2026-10-03), including a transcript of the v3 binary. |
2! |
CAP | a! SWAP !+ !, SWAP in line — executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 7 data cells and 6 return entries. |
C@ |
CAP | See below (D-1). Call-free. Executed on the golden model (2026-10-03). Leaves its caller 8 data cells and 7 return entries. |
C! |
CAP | See below (D-1). Call-free. Executed on the golden model (2026-10-03). |
FILL |
CAP | ( baddr u c -- ): the low byte of c. See below. Nothing for u = 0; for u < 0 nothing is written and NODE-ERROR is set (v3 read a negative count as a huge unsigned one). Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 5 data cells and 5 return entries. Clobbers A and, on error, B. |
MOVE |
CAP | ( addr1 addr2 n -- ) — FORTH-79 (ruled 2026-10-04): n cells from addr1 to addr2, the cell at addr1 first; nothing for n <= 0. The addresses are word addresses (D-1). -if OK drop drop drop ; OK: if DONE push over a! @ over a! ! 1 + push 1 + pop pop -1 + jump OK DONE: drop drop drop ; v3's MOVE moved bytes, in whichever direction did not overwrite what it had yet to read; that is CMOVE and CMOVE>. Executed on the golden model (2026-10-04) against C, overlapping both ways. Leaves its caller 6 data cells and 6 return entries. Clobbers A. |
ERASE |
CAP | 0 FILL, as 0 jump FILL. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. |
CELLS |
IN | Empty (word-addressed, D-1); v3 answers 24 for 3 CELLS. Executed on the golden model (2026-10-03). |
\ byte access on a word-addressed node, little-endian: four bytes to a cell at
\ either cell width (so compiled code is the same, D-9), byte address = 4 * word
\ address + byte index. `@` and `!` inside are the opcodes, addressing through A.
\ C@ is call-free, like C!: one case per byte position, the cell shifted down
\ with a 2/ loop and masked. It replaces a version built on LSHIFT, RSHIFT and
\ SWAP calls, which was correct but left its caller 4 return-stack entries.
: C@ ( baddr -- c )
dup 2/ 2/ a! 3 and \ k A: word address
if K0 -1 + if K1 -1 + if K2
drop @ 23 FOR 2/ UNEXT 255 and ; \ byte 3
K2: drop @ 15 FOR 2/ UNEXT 255 and ; \ byte 2
K1: drop @ 7 FOR 2/ UNEXT 255 and ; \ byte 1
K0: drop @ 255 and ; \ byte 0
\ C! is call-free: one case per byte position. The byte is shifted up with a
\ 2* loop, that byte of the cell cleared with a constant mask, and the two added.
\ It replaces a version built on LSHIFT, RSHIFT, SWAP and OR calls, which was
\ correct but left its caller 4 return-stack entries; this leaves 7.
: C! ( c baddr -- )
dup 2/ 2/ a! 3 and push 255 and pop \ c' k A: word address
if K0 -1 + if K1 -1 + if K2
drop 23 FOR 2* UNEXT @ 4278190080 inv and + ! ; \ byte 3
K2: drop 15 FOR 2* UNEXT @ -16711681 and + ! ; \ byte 2
K1: drop 7 FOR 2* UNEXT @ -65281 and + ! ; \ byte 1
K0: drop @ -256 and + ! ; \ byte 0
: FILL ( baddr u c -- )
-ROT \ c baddr u
-if OK drop drop drop NODE-ERROR b! -1 !b ; \ u < 0
OK: if DONE push over over C! 1 + pop -1 + jump OK
DONE: drop drop drop ;
5.4 Arithmetic
| Word | Fate | v4 definition / notes |
|---|---|---|
+ |
OP | + |
- |
CAP | NEGATE +, NEGATE in line — executed on the golden model (2026-10-03), including a transcript of the v3 binary. |
* |
CAP | UM* drop — executed on the golden model (2026-10-03) against C's wrapping product. Leaves its caller 6 data cells and 5 return entries. |
/ |
CAP | /MOD NIP, as push S>D pop SM/REM push drop pop (/MOD's body and NIP in line, so it runs no deeper than /MOD). Truncates toward zero, as v3. Executed on the golden model (2026-10-03) against C wherever the quotient fits a cell. Leaves its caller 5 data cells and 2 return entries. Division by zero is guarded on the host node (D-15). On the host node (v4/capsule/forth.v4) SM/REM has UM/MOD written into it and keeps its two signs in memory, not on the return stack, so that at nine return entries /, MOD and /MOD worked from four words deep at the prompt, and */ and */MOD, with M* written in, from two and three (with D-17's 32 entries there is room to spare); the answers are the same, checked against C on every pair of the edge values. |
MOD /MOD */ */MOD (§4 versions) |
RET | Shadowed duplicates. Only the §6 versions survive. |
1+ 1- 2+ 2- |
IN | 1 +, -1 +, 2 +, -2 + — executed on the golden model (2026-10-03), including a transcript of the v3 binary. |
2* |
OP | 2* |
2/ |
OP | 2/ |
ABS |
CAP | dup 0< IF NEGATE THEN — executed on the golden model (2026-10-02). |
NEGATE |
CAP | §4. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. |
MIN |
CAP | over over < if B drop drop ; B: drop push drop pop ; — the smaller, signed. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 4 data cells and 6 return entries. |
MAX |
CAP | over over < if A drop push drop pop ; A: drop drop ; — the larger, signed. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 4 data cells and 6 return entries. |
5.5 Logic and comparison
| Word | Fate | v4 definition / notes |
|---|---|---|
AND |
OP | and |
XOR |
OP | xor |
OR |
CAP | §4. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. |
INVERT |
OP | inv |
NOT |
CAP | 0= (FORTH-79 logical not, as in v3), as jump 0=. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. |
LSHIFT |
CAP | BEGIN dup WHILE 1- SWAP 2* SWAP REPEAT drop — executed on the golden model (2026-10-03). |
RSHIFT |
CAP | BEGIN dup WHILE 1- SWAP 2/ MSB inv and SWAP REPEAT drop (clears the sign bit each step; MSB is the cell-width top-bit constant) — executed on the golden model (2026-10-03). |
0= 0< |
CAP | §4. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. |
0<> |
CAP | if Z drop -1 ; Z: ; (0= 0= without calls). Executed on the golden model (2026-10-03), including a transcript of the v3 binary. |
0> |
CAP | -if NN drop 0 ; NN: if Z drop -1 ; Z: ; — two native sign tests in place of dup 0< SWAP 0= OR 0=; correct for the most negative number. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. |
= |
CAP | xor 0= — executed on the golden model (2026-10-02). |
<> |
CAP | xor 0<>, as xor jump 0<>. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. |
< |
CAP | 2DUP xor 0< IF drop 0< ELSE - 0< THEN (overflow-safe) — executed on the golden model (2026-10-02). |
> |
CAP | SWAP <, as SWAP jump < with SWAP in line. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 6 data cells and 6 return entries. |
<= |
CAP | > 0=, as SWAP < jump 0=. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 6 data cells and 6 return entries. |
>= |
CAP | < 0=, as < jump 0=. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 6 data cells and 6 return entries. |
U< |
CAP | §4. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 6 data cells and 7 return entries. |
U> |
CAP | SWAP U< — executed on the golden model (2026-10-02). |
WITHIN |
CAP | ( n low high -- flag ): push over pop < push < inv pop and — low <= n < high, both comparisons signed, which is what v3 computes. It replaces over - push - pop U<, the circular form, which answers differently when low > high (5 10 0 WITHIN is false in v3 and here, true in that form). Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 4 data cells and 5 return entries. |
TRUE |
IN | -1 — executed on the golden model (2026-10-03), including a transcript of the v3 binary. |
FALSE |
IN | 0 — executed on the golden model (2026-10-03), including a transcript of the v3 binary. |
5.6 Mixed-precision arithmetic
A double is two 32-bit cells on a mesh node.
| Word | Fate | v4 definition |
|---|---|---|
M+ |
CAP | S>D D+ — executed on the golden model (2026-10-02). |
M- |
CAP | ( d n -- d ): S>D DNEGATE jump D+, S>D by a sign test in line. n is widened before it is negated, so the most negative n is subtracted correctly; NEGATE M+ would add it. Executed on the golden model (2026-10-03) against C and results recorded from the v3 binary (v3 took the double with its low cell on top; the standard order is kept here, as for M+). Leaves its caller 5 data cells and 5 return entries. |
M* |
CAP | ( n1 n2 -- d ): over over xor push -if A inv 1 + A: push -if B inv 1 + B: pop UM* pop -if P drop jump DNEGATE P: drop ; — the unsigned product of the magnitudes, negated when the signs differ; the sign tests are native. Executed on the golden model (2026-10-03) against C and results recorded from the v3 binary. Leaves its caller 6 data cells and 4 return entries. Clobbers A. |
M/MOD |
CAP | ( d n -- rem quot ): jump SM/REM. Truncating, the remainder with the dividend's sign, as v3. v3 took the double with its low cell on top (dhigh dlow n), the reverse of what its own M* leaves; the standard order is kept here, so M* ... M/MOD composes. Executed on the golden model (2026-10-03), including results recorded from the v3 binary with the double's cells exchanged. Leaves its caller 5 data cells and 3 return entries. |
MOD |
CAP | /MOD drop, with /MOD's body in line: push S>D pop SM/REM drop. The remainder has the dividend's sign, as v3. Executed on the golden model (2026-10-03) against C and results recorded from the v3 binary. Leaves its caller 5 data cells and 2 return entries. Division by zero is guarded on the host node (D-15). |
/MOD |
CAP | push S>D pop SM/REM — executed on the golden model (2026-10-02). |
*/ |
CAP | */MOD NIP, as push M* pop SM/REM push drop pop. Executed on the golden model (2026-10-03). Leaves its caller 5 data cells and 2 return entries. |
*/MOD |
CAP | ( n1 n2 n3 -- rem quot ): push M* pop jump SM/REM — the product is a full double, so the answer is exact whenever the quotient fits a cell. v3 multiplied in one cell at 64-bit cells and so was right only while n1 * n2 fitted one; the two agree there. Executed on the golden model (2026-10-03) against the identity quot * n3 + rem = n1 * n2 on every combination of the edge values, and results recorded from the v3 binary. Leaves its caller 5 data cells and 2 return entries. |
5.7 Double-cell numbers
| Word | Fate | v4 definition |
|---|---|---|
S>D |
CAP | dup 0< — executed on the golden model (2026-10-02). |
D+ |
CAP | push over push push drop pop over over xor -if L1 drop + -if C1 jump C0 L1: drop over -if L2 drop + jump C1 L2: drop + C0: pop pop + ; C1: pop pop + 1 + ; — call-free. The carry out of the low cells is found by sign tests: if their top bits differ, there is a carry exactly when the sum's top bit is clear; if they match, exactly when both are set. Executed on the golden model (2026-10-02); leaves its caller 6 data cells under its arguments and 5 return entries. Replaces push SWAP push over + 2DUP U> ROT drop NEGATE pop pop + +, which was exact but left 1 return entry. |
DNEGATE |
CAP | inv over if L1 drop push inv 1 + pop ; L1: drop 1 + ; — call-free: ~d + 1, carrying into the high cell exactly when the low cell is 0. Executed on the golden model (2026-10-02). Replaces inv SWAP inv SWAP 1 0 D+, which left a caller no return-stack room, so DABS could not run at all. |
D- |
CAP | DNEGATE D+ — executed on the golden model (2026-10-02). |
DABS |
CAP | dup 0< IF DNEGATE THEN — executed on the golden model (2026-10-02). |
D0= |
CAP | OR 0= — executed on the golden model (2026-10-02). |
D0< |
CAP | NIP 0<, as push drop pop jump 0<. Executed on the golden model (2026-10-03), including results recorded from the v3 binary. |
D= |
CAP | D- D0= — executed on the golden model (2026-10-02). |
D< |
CAP | ROT 2DUP = IF 2DROP U< ELSE SWAP < NIP NIP THEN — executed on the golden model (2026-10-02). |
(D<) |
CAP | ( d1 d2 -- d1 d2 flag ), call-free; internal to DMAX and DMIN. Copies of the high cells go on top; if they differ the flag comes from them, else from the low cells unsigned. The value tested always has its top bit set exactly when d1 < d2: ah (high signs differ), ah - bh (agree), bl (low top bits differ), al - bl (agree); x - y with y on top is push inv pop + inv. dup push push over pop over over xor if TIE drop over over xor -if HS drop drop jump S1 HS: drop push inv pop + inv S1: -if N1 drop -1 jump D1 N1: drop 0 D1: pop SWAP ; TIE: drop drop drop dup push push over pop over over xor -if LS drop NIP jump S2 LS: drop push inv pop + inv S2: -if N2 drop -1 jump D2 N2: drop 0 D2: pop pop ROT ; with SWAP, NIP and ROT in line. Executed on the golden model (2026-10-02). |
DMAX |
CAP | (D<) if L drop push push drop drop pop pop ; L: drop drop drop ; Executed on the golden model (2026-10-02). Replaces 2OVER 2OVER D< IF 2SWAP THEN 2DROP, which needs 8 data cells plus D<'s 2: the whole 10-deep data stack, so it failed whenever the caller held anything at all (D-2). |
DMIN |
CAP | (D<) if L drop drop drop ; L: drop push push drop drop pop pop ; Executed on the golden model (2026-10-02); replaces 2OVER 2OVER D< 0= IF 2SWAP THEN 2DROP for the same reason as DMAX. |
D2* |
CAP | 2* over -if P drop 1 + jump J P: drop J: push 2* pop — the low cell's top bit enters the high cell; a native sign test in place of over 0< NEGATE OR. Executed on the golden model (2026-10-03), including results recorded from the v3 binary. |
D2/ |
CAP | push a! 0 pop +* push drop a pop — one +* with S = 0 is an exact arithmetic right shift of T:A, as in Q.FROM-INT. It replaces dup 1 and push 2/ SWAP 1 RSHIFT pop IF MSB OR THEN SWAP. Executed on the golden model (2026-10-03), including results recorded from the v3 binary. Clobbers A. |
2DROP |
IN | drop drop — executed on the golden model (2026-10-03). |
2DUP |
IN | over over — executed on the golden model (2026-10-03). |
2SWAP |
CAP | ROT push ROT pop, with ROT and SWAP in line so it makes no calls. Executed on the golden model (2026-10-02). Called, ROT and SWAP left its caller 2 return entries; in line, 4. |
2OVER |
CAP | push push 2DUP pop pop 2SWAP — executed on the golden model (2026-10-02). |
2ROT |
CAP | push push 2SWAP pop pop jump 2SWAP. Executed on the golden model (2026-10-03), including a result recorded from the v3 binary. With six cells of its own it leaves its caller 3 data cells and 1 return entry. |
2>R |
IN | SWAP push push — executed on the golden model (2026-10-03) with 2R@ and 2R>; the high cell is on top of the return stack, as in v3. |
2R> |
IN | pop pop SWAP — see 2>R. |
2R@ |
IN | pop pop 2DUP push push SWAP — see 2>R. |
5.8 Number formatting and output
All output reaches the console through EMIT (DEV).
| Word | Fate | Notes |
|---|---|---|
<# # #S HOLD SIGN #> |
CAP | Pictured output over UM/MOD and a 64-byte hold buffer, filled backwards from its end HEND through the pointer HLD. Standard stack effects (v3 took its double low cell on top, and its tolerant #> popped ud only if present; neither is kept). Otherwise v3's behaviour: digits 0–9 then A–Z; BASE outside 2–36 reads as 10; 63 characters; HOLD of a value outside 0–255 or into a full buffer stores nothing and sets NODE-ERROR (D-13). Definitions below. Executed on the golden model (2026-10-03) against a C reference in bases 2, 3, 8, 10, 16, 36 and four invalid ones. <# #S #> leaves its caller 4 data cells and 3 return entries; the signed picture dup push ABS 0 <# #S pop SIGN #> leaves 2 return entries. #, #S, HOLD and SIGN clobber A and B. |
HLD |
CAP | Variable: byte address of the first held character. HLD ( -- addr ) is the variable's word address, a literal. Not a v3 word. Executed on the golden model (2026-10-03): after <# and each HOLD or #, HLD @ is the address #> returns. |
. .R U. U.R D. D.R |
CAP | Built on pictured output and TYPE; definitions below. ., U. and D. print the number in the current base, then one space. The .R words right-justify it in width columns, print a wider number whole, pad nothing for width <= 0, and print no space after it: that is FORTH-79's reference word (ruled 2026-10-04); v3 printed a space after these too. Where v4 parts from v3: a double is ( lo hi ), where v3's D. took the low cell on top; D. prints the whole double, where v3 printed DOUBLE-OVERFLOW unless it fitted one signed cell (they agree whenever it does); printing honours the BASE variable, where v3 printed in a host copy that only DECIMAL, HEX and OCTAL set, so n BASE ! changed v3's input base but not its output; and the number is built in the 63-character hold buffer, so a longer one — a 64-bit cell in base 2, a large double in a small base — loses its leading characters and sets NODE-ERROR (D-13), where v3 printed from a private 80-character buffer. Executed on the golden model (2026-10-03) against a C reference in the ten bases of the pictured-output test and eleven field widths, and against five transcripts of the v3 binary (a sixth, the extreme cells, at 64-bit cells). Each leaves its caller 4 data cells; U. and U.R leave 3 return entries, the signed words 2, their sign waiting on the return stack. All clobber A and B. |
(W) |
CAP | Variable, 2 cells: the field width of the .R words, and whether a blank follows the number. Like BASE and HLD, it lives in node memory, so the width is on neither stack while the picture runs. |
.S |
CC | As v3: <depth> , then every value on the stack, the deepest first, each followed by a blank, then a new line; the stack is left as it was. Built on DEPTH, PICK and . (D-16). It works on the stack it is printing, so it needs six cells of it free: on the host node's 32 it prints a stack of up to 26 values, and a fuller one is a stack overflow. Source in v4/capsule/numout.v4; executed on the golden model's host node (2026-10-04), including transcripts of the v3 binary. |
? |
CAP | ( addr -- ): a! @ jump . — the cell at word address addr (D-1), printed as . prints it. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 4 data cells and 2 return entries. Clobbers A and B. |
DUMP |
CAP | ( baddr u -- ): u bytes from byte address baddr, sixteen to a line, in v3's format: the address in hex, ": ", each byte as two hex digits and a space (three spaces where the line runs out), " |", the bytes as characters with . for anything outside 32–126, "|" and a new line. Always hex, whatever BASE is, and BASE is put back. The address is two hex digits per byte of a cell — 8 at 32-bit cells, 16 at 64, which is v3's width. Nothing for u = 0; for u < 0 nothing is printed and NODE-ERROR is set, as TYPE does, where v3 raised its error flag. Addresses are not range-checked (open, node.h). Definition below. Executed on the golden model (2026-10-03) against a C reference at six alignments and every length 0–50, and at 64-bit cells against a transcript of the v3 binary, byte for byte. Leaves its caller 4 data cells and 4 return entries. Clobbers A and B. |
(DP) |
CAP | Variable, 4 cells: DUMP's saved BASE, address, count and column. |
BASE |
CAP | Variable. BASE ( -- addr ) is its word address (D-1), a literal. Executed on the golden model (2026-10-03). Storing to it changes what is printed, which it did not in v3 (see .). |
DECIMAL HEX OCTAL |
CAP | 10 BASE !, 16 BASE !, 8 BASE !, with ! in line as a! !. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Each leaves its caller 8 data cells and 8 return entries. Clobber A. |
\ pictured output. HEND is the byte address just past the hold buffer.
: (BASE) ( -- b ) BASE b! @b dup -2 + -if L1 drop drop 10 ;
L1: drop dup -37 + -if L2 drop ; L2: drop drop 10 ;
: <# ( -- ) HEND HLD b! !b ;
: HOLD ( c -- ) dup -256 and if OKC drop jump ERR
OKC: drop HLD b! @b -(HEND-62) + -if ROOM drop
ERR: drop NODE-ERROR b! -1 !b ;
ROOM: drop HLD b! @b -1 + dup !b jump C!
: SIGN ( n -- ) -if L1 drop 45 jump HOLD L1: drop ;
: # ( ud1 -- ud2 )
0 (BASE) UM/MOD -ROT \ qhi lo rem1
(BASE) UM/MOD -ROT \ qlo qhi rem
dup -10 + -if L1 drop 48 + jump L2 L1: drop 55 + L2: jump HOLD
: #S ( ud -- 0 0 ) L: # over over OR if L1 drop jump L L1: drop ;
: #> ( ud -- baddr u ) drop drop HLD b! @b HEND over push inv pop + inv ;
# divides the double by the base in two UM/MOD steps, the high cell first and its remainder
leading the low cell; the last remainder is the digit. HOLD, SIGN and # end with a jump to the
next word rather than a call, so C! returns straight to their caller and two return-stack entries
are saved. # keeps the high quotient under the second division on the data stack, not on the
return stack. -ROT is in line (SWAP push SWAP pop).
\ number output. ABS is in line: -if A inv 1 + A:
: .R ( n width -- )
0 (W) 1 + b! !b \ no blank after it
BODY: (W) b! !b dup push ABS 0 <# #S pop SIGN #> \ baddr u
TAIL: (W) b! @b -if POS drop jump OUT \ width < 0
POS: over - SPACES \ width - u spaces
OUT: TYPE (W) 1 + b! @b if DONE drop jump SPACE
DONE: drop ;
: . ( n -- ) -1 (W) 1 + b! !b 0 jump BODY \ a blank after it
: U.R ( u width -- ) 0 (W) 1 + b! !b UBODY: (W) b! !b 0 <# #S #> jump TAIL
: U. ( u -- ) -1 (W) 1 + b! !b 0 jump UBODY
: D.R ( d width -- ) 0 (W) 1 + b! !b
DBODY: (W) b! !b dup push -if A DNEGATE A: <# #S pop SIGN #> jump TAIL
: D. ( d -- ) -1 (W) 1 + b! !b 0 jump DBODY
\ DUMP. DIGITS is two per byte of a cell.
: (DB) ( -- c ) (DP) 1 + b! @b (DP) 3 + b! @b + jump C@ \ the byte in this column
: DUMP ( baddr u -- )
-if OK drop drop NODE-ERROR b! -1 !b ; \ u < 0
OK: (DP) 2 + b! !b (DP) 1 + b! !b \ count, address
BASE b! @b (DP) b! !b 16 BASE b! !b \ hex
LINE: (DP) 2 + b! @b if DONE drop
(DP) 1 + b! @b 0 <# DIGITS (DP) 3 + b! !b \ the address
AD: # (DP) 3 + b! @b -1 + dup !b if ADX drop jump AD
ADX: drop #> TYPE 58 EMIT SPACE
0 (DP) 3 + b! !b \ the bytes in hex
HX: (DP) 2 + b! @b (DP) 3 + b! @b inv + -if HAVE \ count - column - 1
drop SPACE SPACE SPACE jump HN
HAVE: drop (DB) 0 <# # # #> TYPE SPACE
HN: (DP) 3 + b! @b 1 + dup !b -16 + -if HXX drop jump HX
HXX: drop SPACE 124 EMIT
0 (DP) 3 + b! !b \ the bytes as characters
CH: (DP) 2 + b! @b (DP) 3 + b! @b inv + -if HAVC jump CHX
HAVC: drop (DB)
dup -32 + -if GE drop drop 46 jump EM
GE: drop dup -127 + -if BIG drop jump EM
BIG: drop drop 46
EM: EMIT
(DP) 3 + b! @b 1 + dup !b -16 + -if CHX drop jump CH
CHX: drop 124 EMIT CR
(DP) 1 + b! @b 16 + !b \ next line
(DP) 2 + b! @b -16 + -if MORE jump DONE
MORE: !b jump LINE
DONE: drop (DP) b! @b BASE b! !b ;
Everything DUMP keeps between words — the address, the count, the column and the saved BASE — is
in the variable (DP), so the stacks carry only what each picture and TYPE need, and no loop count
sits on the return stack across a call.
In the capsule. v4/capsule/numout.v4 holds these words for the host node — <# # #S HOLD SIGN #>, . .R U. U.R D. D.R, ?, SPACES, DECIMAL HEX OCTAL — each the definition above, with the shared body and tail of the number words as words of their own ((.BODY), (U.BODY), (D.BODY), (.TAIL)), since a jump cannot land inside another word. Executed from the prompt on the golden model's host node (2026-10-04), including transcripts of the v3 binary (tests/test_host_quit.c). At 64-bit cells a 64-digit binary number is one digit more than the hold buffer's 63 (D-13): 63 digits are printed and the line ends with ERROR.
There is one picture for signed singles, one for unsigned and one for doubles; each plain word is
its .R word with a width of 0 and the blank flag set, entered by a jump past where .R clears
that flag, and all six share one tail, which prints the blank only when the flag is set. The width is tested for sign before width - u, which could wrap for a very negative
width and print a flood of spaces.
5.9 Strings, parsing, and input
| Word | Fate | Notes |
|---|---|---|
COUNT |
CAP | ( baddr -- baddr+1 c ): dup C@ push 1 + pop. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 7 data cells and 6 return entries. Clobbers A. |
CMOVE |
CAP | ( src dst u -- ), low byte first. See below. Nothing for u = 0; for u < 0 nothing is written and NODE-ERROR is set, where v3 raised its error flag. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 5 data cells and 5 return entries. |
CMOVE> |
CAP | ( src dst u -- ), high byte first. See below. Errors as CMOVE. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 4 data cells and 5 return entries. |
BLANK |
CAP | 32 FILL, as 32 jump FILL. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. v3's counted-string auto-detection is not kept. |
-TRAILING |
CAP | ( baddr u -- baddr u' ). See below. A negative count reads as 0, as v3. v3's counted-string auto-detection is not kept. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 6 data cells and 6 return entries. |
COMPARE |
CAP | ( a1 u1 a2 u2 -- n ): -1, 0 or 1 by the first differing byte, unsigned, else the shorter string is less. See below. Negative counts read as 0, as v3. v3's counted-string auto-detection is not kept. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 4 data cells and 5 return entries. Clobbers A and B. |
SEARCH |
CAP | ( a1 u1 a2 u2 -- a3 u3 flag ): the rest of string 1 from the first place string 2 occurs and -1, or string 1 and 0; an empty string 2 is found at the start. See below; the inner comparison is in line, not a call to COMPARE. Same notes. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 3 data cells and 5 return entries. Clobbers A and B. |
SCAN SKIP |
CAP | ( baddr u c -- baddr' u' ): SCAN stops at the first byte equal to the low byte of c (or at the end, with u' = 0); SKIP stops at the first byte not equal to it. See below. A negative count reads as 0, as v3. v3's counted-string auto-detection is not kept. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Each leaves its caller 5 data cells and 5 return entries. Clobber A. |
(S) |
CAP | Variable, 4 cells: COMPARE's two lengths; SEARCH's string 2 and the whole of string 1. |
BL |
IN | 32 — executed on the golden model (2026-10-04). |
EXPECT QUERY |
CC | Built on KEY (DEV); source in v4/capsule/input.v4. FORTH-79 (ruled 2026-10-04). EXPECT ( baddr n -- ) stores characters from baddr upward until a new-line (taken, not stored) or until n have been received, then a zero; no action for n <= 0; a line longer than n leaves the rest to be read next. It also sets SPAN, which is not FORTH-79 but is v3's. QUERY is TIB 80 EXPECT 0 >IN !. v3's EXPECT was C's fgets and took n-1 characters, and its QUERY took 1024. Executed on the golden model's host node (2026-10-04) against C for every size and line length. EXPECT is what the prompt reads every line with, on top of whatever the user has left on the stack, so it keeps where it is in memory: it leaves its caller 7 data cells and 7 return entries. A line of exactly 80 characters fills QUERY's count before its new-line arrives, so the new-line is read as an empty line after it; a longer line is read as 80 characters and then the rest. |
SPAN TIB >IN SOURCE |
CC | Interpreter state on the host node. TIB is the buffer's byte address and >IN and SPAN their variables' word addresses, all in line; SOURCE ( -- baddr u ) is TIB and SPAN @, a negative SPAN read as 0. Executed on the golden model's host node (2026-10-04). |
WORD ENCLOSE |
CC | Parser; source in v4/capsule/input.v4. WORD ( c -- baddr ) is FORTH-79 (ruled 2026-10-04): characters are taken from TIB until the delimiter c or the end of the text, leading delimiters ignored, and stored as a counted string; the delimiter met (c, or a zero if the text ran out) is stored after them, uncounted; >IN is left just past it; with nothing left the count is 0. The count is a byte, so a word over 255 characters is cut to 255; the buffer is 257 bytes. v3's WORD skipped every delimiter after the word, always stored a zero, and cut at 62. ENCLOSE ( baddr c -- baddr n1 n2 n3 ), not a FORTH-79 word, works on a zero-terminated string as v3's. Executed on the golden model's host node (2026-10-04) against C, and ENCLOSE against values recorded from the v3 binary. WORD keeps what it works on in memory and has at most three cells of its own on the data stack: it leaves its caller 7 data cells and 6 return entries. (PARSE) ( c -- baddr ) is WORD without the skipping of leading delimiters: the text starts at >IN and may be empty. It is what ." and ABORT" use, so that ." " is an empty string. |
NUMBER CONVERT |
CC | FORTH-79, not v3 (ruled 2026-10-04); source in v4/capsule/input.v4. CONVERT ( d1 baddr1 -- d2 baddr2 ) takes the characters from baddr1 + 1 on as digits in the current BASE, in either case, accumulating each into the double after multiplying it by BASE, and stops at the first that is not one; the double is ( lo hi ). v3's was base 10 only, started at baddr1, and took the double low cell on top. NUMBER ( baddr -- d ) is the counted string as a signed double in the current BASE, with an optional leading minus; anything else gives 0 and sets NODE-ERROR. v3's returned ( n flag ) in base 10. Executed on the golden model's host node (2026-10-04) against C in eight bases, including a digit that carries out of the low cell. NUMBER leaves its caller 3 data cells and 2 return entries; the interpreter does not use it (it has a single-cell conversion of its own that needs far less of the stack). |
S" (s") ['] |
CC | Compiler words. |
LITERAL [LITERAL] (placeholders) |
RET | The working LITERAL is in §5.17. |
\ SWAP and the subtractions ( - ) are in line.
: CMOVE ( src dst u -- )
-if OK drop drop drop NODE-ERROR b! -1 !b ; \ u < 0
OK: if DONE push \ src dst R: u
over C@ over C! 1 + push 1 + pop pop -1 + jump OK
DONE: drop drop drop ;
: CMOVE> ( src dst u -- )
-if OK drop drop drop NODE-ERROR b! -1 !b ; \ u < 0
OK: if DONE -1 + push \ src dst R: i = u - 1
over pop dup push + C@ \ src dst c
over pop dup push + C! \ src dst
pop jump OK
DONE: drop drop drop ;
: -TRAILING ( baddr u -- baddr u' )
-if L drop 0 ; \ u < 0
L: if DONE over over + -1 + C@ -32 + if SP drop ; \ not a space
SP: drop -1 + jump L
DONE: ;
: COMPARE ( a1 u1 a2 u2 -- n )
-if A drop 0 A: (S) 1 + b! !b \ a1 u1 a2
SWAP -if B drop 0 B: dup (S) b! !b \ a1 a2 u1
(S) 1 + b! @b \ a1 a2 u1 u2
over over - -if GE drop drop jump M \ the smaller
GE: drop push drop pop
M: \ a1 a2 m
L: if EQ push \ a1 a2 R: m
over C@ over C@ - if SAME \ c1 - c2
-if GT drop drop drop pop drop -1 ;
GT: drop drop drop pop drop 1 ;
SAME: drop 1 + push 1 + pop pop -1 + jump L
EQ: drop drop drop (S) b! @b (S) 1 + b! @b - \ u1 - u2
-if NN drop -1 ;
NN: if ZZ drop 1 ;
ZZ: ;
: SEARCH ( a1 u1 a2 u2 -- a3 u3 flag )
-if A drop 0 A: (S) 1 + b! !b (S) b! !b \ a1 u1 (S): a2 u2
-if B drop 0 B: dup (S) 3 + b! !b over (S) 2 + b! !b \ a u (S)+2: a1 u1
L: dup (S) 1 + b! @b - -if TRY \ u - u2
drop drop drop (S) 2 + b! @b (S) 3 + b! @b 0 ; \ not found
TRY: drop over (S) b! @b (S) 1 + b! @b \ a u p q k
I: if MATCH push \ a u p q R: k
over C@ over C@ xor if SAME
drop drop drop pop drop push 1 + pop -1 + jump L \ next place
SAME: drop 1 + push 1 + pop pop -1 + jump I
MATCH: drop drop drop -1 ;
: SCAN ( baddr u c -- baddr' u' )
255 and push -if L drop 0 \ baddr u R: c
L: if DONE over C@ pop dup push xor if FOUND
drop push 1 + pop -1 + jump L
FOUND: drop
DONE: pop drop ;
: SKIP ( baddr u c -- baddr' u' )
255 and push -if L drop 0 \ baddr u R: c
L: if DONE over C@ pop dup push xor if SAME drop jump DONE
SAME: drop push 1 + pop -1 + jump L
DONE: pop drop ;
5.10 Terminal I/O
| Word | Fate | Notes |
|---|---|---|
EMIT |
DEV | Console service: one-character message. Until the mesh exists it is a store to the CONSOLE-TX register (§7): CONSOLE-TX b! !b. As in v3, the low byte of the cell is the character. Executed on the golden model (2026-10-03). Clobbers B. |
KEY |
DEV | Console service: blocking receive. Until the mesh exists it reads the CONSOLE-STATUS and CONSOLE-RX registers (§7): L: CONSOLE-STATUS b! @b if WAIT drop CONSOLE-RX b! @b ; WAIT: drop jump L — it asks whether a character is pending until one is, then takes it. The character is 0–255. v3's KEY returned −1 at the end of its input; here there is no end of input, and KEY waits. Executed on the golden model (2026-10-03), including a transcript of the v3 binary, and shown still waiting after 5000 instruction words with nothing pending. Leaves its caller 9 data cells and 8 return entries. Clobbers B. |
?TERMINAL |
DEV | Console service: non-blocking status. Until the mesh exists: CONSOLE-STATUS b! @b — −1 when a character is pending, 0 when not, as v3; it takes nothing. Executed on the golden model (2026-10-03). Clobbers B. |
TYPE |
CAP | ( baddr u -- ). Loop of C@ EMIT, or one string message to the console node: -if OK drop drop NODE-ERROR b! -1 !b ; OK: if DONE over C@ EMIT push 1 + pop -1 + jump OK DONE: drop drop ; — nothing for u = 0; for u < 0 nothing is printed and NODE-ERROR is set, where v3 printed nothing and raised its error flag. An address outside the node's memory is an address fault (D-14). Executed on the golden model (2026-10-03). Leaves its caller 6 data cells and 6 return entries. Clobbers A and B. |
CR |
CAP | 10 EMIT, as 10 jump EMIT — executed on the golden model (2026-10-03). Character 10, as v3; the console turns it into a new line. |
SPACE |
CAP | BL EMIT, as 32 jump EMIT — executed on the golden model (2026-10-03). |
SPACES |
CAP | ( n -- ): -if L drop ; L: if DONE SPACE -1 + jump L DONE: drop ; — n spaces, none for n <= 0, as v3. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 7 data cells and 7 return entries. Clobbers B. |
." (.") |
CC | FORTH-79; source in v4/capsule/quit.v4. ." text" takes the text up to the next " (none at all is allowed; with no closing " it is the rest of the line). Interpreting, it prints it. Compiling, it lays a call to (.") and after it the text as a counted string — count byte, characters, four to a cell, the last cell filled with zeros. (.") finds the string by the return address its call left and returns to the cell after it: pop 4* dup C@ L: if DONE push 1 + dup C@ EMIT pop -1 + jump L DONE: drop 4/ 1 + push ;. It takes the return address off before it calls anything, so a word that prints text goes no deeper than one that calls any other word. v3's (do-string) has no counterpart. Executed on the golden model's host node (2026-10-04), every length from 0 to 12 and 76, including transcripts of the v3 binary. |
5.11 Blocks and mass storage
The storage service belongs to the Artemis role, now a device node.
| Word | Fate | Notes |
|---|---|---|
BLOCK BUFFER UPDATE SAVE-BUFFERS EMPTY-BUFFERS FLUSH |
DEV | Block-service messages. Buffers live in the requesting node's RAM or in DDR. |
LOAD THRU --> |
CC | The interpreter reads blocks through the service. |
LIST |
CAP | BLOCK plus TYPE. |
SCR |
CAP | Variable. |
BLK-CONFIRM-FORMAT RELOCATE-BLOCK |
DEV | Owner-only storage messages. |
BLK-ACL-ALLOW@ BLK-ACL-ALLOW! BLK-ACL-TTL@ BLK-ACL-TTL! |
HERA | ACL state is held by Hera. |
BLK-OWNER@ |
HERA | Read-only, as in v3. |
BLK-ATTACH |
RET | Took a raw host pointer. Replaced by an attach message from the device node. |
5.12 Dictionary space
| Word | Fate | Notes |
|---|---|---|
HERE ALIGN ALLOT , C, 2, PAD LATEST |
CC | The dictionary lives on the host node; source in v4/capsule/dict.v4. The dictionary pointer DP is a byte address, so C, packs four characters to a cell; HERE is the next whole cell (a word address); , and ALLOT align first. An address unit is a cell (D-1), so ALLOT counts cells where v3's counted bytes; a negative count gives space back. 2, stores the low cell first, as v3. Going outside the dictionary space stores nothing, moves nothing and sets NODE-ERROR. PAD is a fixed scratch area, as in v3, given as a byte address. LATEST is the xt of the newest entry, 0 if there is none (v3's pushes HERE, which looks like a bug; reported, not changed). Executed on the golden model's host node (2026-10-04). , calls nothing and leaves its caller 7 data cells and 8 return entries; ALLOT leaves 7 and 7. |
SP@ SP! |
RET | A stack cell has no address (D-2, D-16). DEPTH, and a store to DSTACK-DEPTH to empty the stack, are what remain of them. |
5.13 Dictionary manipulation
| Word | Fate |
|---|---|
FIND |
CC |
' |
CC |
>LINK LFA LINK> >NAME NFA NAME> CFA PFA >BODY TRAVERSE |
CC |
SMUDGE HIDDEN |
CC |
INTERPRET |
CC |
An entry, as v4/capsule/dict.v4 builds it. A word's execution address (xt) is the address of its
code, and everything else is found from it:
name the count byte, then the characters, four bytes to a cell, zero-padded to a whole cell
xt - 3 flags: 1 immediate, 2 hidden, 4 data
xt - 2 the word address of the name
xt - 1 the link: the xt of the entry before this one, 0 for the first
xt the code
A name holds at most 31 characters. A data word (one made by CREATE, VARIABLE or CONSTANT) is
one whose code is a single call to its run-time routine, with its parameter field in the cell after
it, xt + 1; for any other word the parameter field is the code itself.
5.14 Vocabularies
| Word | Fate |
|---|---|
VOCABULARY DEFINITIONS CONTEXT CURRENT FORTH ORDER (FIND) |
CC |
5.15 System
| Word | Fate | Notes |
|---|---|---|
( \ |
CC | ( is 41 WORD drop; \ is SPAN @ >IN !. In v4/capsule/input.v4 under the names PAREN and BACKSLASH. Executed on the golden model's host node (2026-10-04). |
EXECUTE |
CAP | push ; (tail-jumps to the xt; the xt returns to EXECUTE's caller). Executed on the golden model's host node (2026-10-04). |
NOP |
OP | nop |
QUIT |
CC | FORTH-79; source in v4/capsule/quit.v4. Clears the return stack, sets execution mode, returns control to the terminal; no message. It is the prompt: after a new line it prints ok> , reads a line with QUERY, clears NODE-ERROR, runs INTERPRET, prints ok — or, if NODE-ERROR is set, ends any definition that was open and prints ERROR — and goes round again, for as long as the node runs. The text is v3's. The line is not sent back; the terminal shows what is typed. It empties the return stack first, by a store to RSTACK-DEPTH (D-16), so it works from however deep, with the return stack full. The data stack is left alone. A definition open when QUIT runs is ended and stays hidden. Executed on the golden model's host node (2026-10-04): the node is started at QUIT, fed characters and its output read, including 16 sessions recorded from the v3 binary. v3's QUIT goes on with the rest of the line and cannot be compiled into a definition. |
ABORT |
CC | FORTH-79; source in v4/capsule/quit.v4. As QUIT, and it empties the data stack too (a store to DSTACK-DEPTH, D-16); the line it stops ends with ok, as v3. Executed on the golden model's host node (2026-10-04), from six calls down and from inside two loops, forty times over, including transcripts of the v3 binary. |
ABORT" (ABORT") |
CC | Not FORTH-79 (it is FORTH-83's); kept because v3 has it. ABORT" text" ( flag -- ): if the flag is not zero, print the text, start a new line and ABORT. Compiled as ." is, with (ABORT") as the run-time word; it also works at the prompt. Executed on the golden model's host node (2026-10-04). v3's crashes (SIGSEGV) when a word compiled with it runs, and at the prompt prints the text and carries on with the line. |
COLD WARM |
CC | |
BYE REBOOT |
HERA | |
SAVE-SYSTEM |
HERA | Snapshot becomes a capsule-image request. |
WORDS VLIST SEE |
CC | |
PAGE |
DEV | |
79-STANDARD |
CC |
5.16 Line editor
| Word | Fate |
|---|---|
L S SHOW EDIT |
CC |
5.17 Defining words and the compiler
| Word | Fate | Notes |
|---|---|---|
: ; EXIT IMMEDIATE STATE [ ] LITERAL COMPILE [COMPILE] |
CC | Source in v4/capsule/compile.v4, all to FORTH-79. : makes an entry and starts compiling; the word cannot be found until ;, so a redefinition can use the old word. ; lays the return, reveals the word and stops compiling. LITERAL compiles the number on the stack when compiling. COMPILE xxx lays code that compiles xxx when the word it is in runs; [COMPILE] xxx compiles xxx though it is immediate. Executed on the golden model's host node (2026-10-04) against results recorded from the v3 binary; v3's COMPILE does not work (: C1 COMPILE DUP ; IMMEDIATE : U3 C1 + ; gives DUP: Stack underflow), reported, not fixed. |
CREATE VARIABLE CONSTANT DOES> |
CC | FORTH-79. A data word's code is one instruction word, a call to its run-time routine, and its parameter field is the cell after it: (DOVAR) is pop ; (the call's return address is the parameter field's address) and (DOCON) is pop a! @ ;. VARIABLE allots one cell, set to 0. DOES> lays a call to (DOES) and then pop: (DOES), run by the defining word, rewrites the newest entry's code into a call to the code after it and returns from the defining word; that code's pop fetches the parameter field address. Executed on the golden model's host node (2026-10-04) against results recorded from the v3 binary. |
FORGET FENCE |
CC | |
LIT |
OP | @p |
does_rt |
RET | Internal helper; DOES> is implemented by the compiler capsule. |
How a word is compiled. v4 code is native: a colon definition is instruction words, and
compiling a word into one lays down a call to it. Two flags on an entry change that. An in-line
word's code is a straight run of opcodes and literals ending in ;, and that run is copied in place
of a call; this is fate IN (§0), and everything that touches the return stack must be in line, since a
call would bury what it works on. A compile-only word may not be executed by the interpreter.
v4/capsule/forth.v4 holds the first words of the vocabulary flagged this way (DUP, +, >R, I,
LEAVE, @, the in-line variables and so on).
The stacks and the compiler. The interpreter and compiler run on the same stacks as the user's words, with the user's values beneath them. The capsule was written for stacks of ten cells and nine entries (D-2), and is as sparing as that needed; the host node's are now 32 and 32 (D-17). The two habits built into the capsule stay, and the measurements below are for the host node as it is now:
- Every word of the capsule keeps what it works on in memory and has at most three cells of its own on the data stack. Measured on the golden model: a line may have 28 values on the stack while the interpreter reads its next word, 29 may wait there while the next line is typed and interpreted (the prompt and the interpreter take no more than four cells), and defining and running a word leaves 26 cells under it untouched. (At ten cells these were 6, 6 and 4.)
- The capsule's words call each other as little as they can: byte access shifts without a loop,
opcodes are shifted into the word being built instead of placed by a counted shift, and the long
jobs (laying a loop's end, making a data word) are single words reached by a jump. Measured on the
golden model: the prompt has one return entry under each line, its call of
INTERPRET, so words run from the prompt may call each other 31 deep (8 deep at nine entries), and one more is reported as a return stack overflow; a line that runs a word which calls nothing uses four entries; and a defining word (CREATE … DOES>) works from the prompt, from a word, and from a word that calls that. ADOloop takes two return entries.*is therefore notUM* dropon the host node but N steps of+*with only the count on the return stack; it gives the same low cell (what+*loses at the top ofTtakes N shifts to reach the bit that moves intoA, and the loop ends first).
A programme that v3 runs with more than that on its stacks does not run here; that is D-2, not the compiler.
The code generator (v4/capsule/codegen.v4) is what these words are built on. It packs opcodes,
literals and branches into instruction words at HERE, by the rules of §1.2 and §2: slots filled left
to right, unused slots nop, a literal's value in the cell after its word, ; ex and any branch
closing the word. A branch goes only in a slot whose address field reaches all of the node's memory
(slots 0–2 on a 16,384-word host node, 0–3 on a 1,024-word one); otherwise it starts the next word, so
no reach check is ever needed.
| Word | Does |
|---|---|
(OP,) |
( op -- ) any opcode that is neither a branch nor @p |
(LIT,) |
( x -- ) @p and its value |
(LABEL) |
( -- addr ) closes the word being built; the address of the next: a branch target |
(BRANCH,) |
( target op -- ) a branch to a known address; (JUMP,) and (CALL,) ( addr -- ) are the two commonest |
(BRANCH>) (RESOLVE) |
( op -- ref ) a branch whose address is not known yet, and ( target ref -- ) filling it in |
(FLUSH) (CG-RESET) |
append the word being built; forget it |
Executed on the golden model's host node (2026-10-04) by laying the same programmes down with the text assembler on one node and with the code generator, running, on another, and comparing memory word for word: every opcode, literals and branches in every slot position, and 600 random programmes. What it lays down was then run.
5.18 Control flow
| Word | Fate | v4 definition / notes |
|---|---|---|
IF ELSE THEN BEGIN UNTIL AGAIN WHILE REPEAT DO ?DO LOOP +LOOP |
CC | Compile-time structure words, immediate and compile-only; source in v4/capsule/compile.v4. Expansions per §2 and below. What an opening word leaves for the word that closes it goes on a control-flow stack in memory (32 cells), not on the data stack, with a number saying which word left it; a closing word that finds the wrong number, or nothing, abandons the line. A loop that tests at its end (UNTIL) must drop the flag on both ways out, so BEGIN lays jump L0 Ld: drop L0: and UNTIL lays if Ld drop. Executed on the golden model's host node (2026-10-04): the code laid down for each is word for word what the text assembler makes of the expansion given here, and the programmes run give what the v3 binary gave. |
LEAVE I J UNLOOP |
CC | In-line words (below), compile-only. |
CASE OF ENDOF ENDCASE |
CC | |
EXIT |
OP | ; |
(BRANCH) |
OP | jump |
(0BRANCH) |
IN | if L … drop (§2) — executed on the golden model (2026-10-03) as IF 1 ELSE 2 THEN, including results recorded from the v3 binary. |
(DO) |
IN | over push push drop (R: limit index) — SWAP push push without the SWAP. The body always runs once, as v3. Executed on the golden model (2026-10-03). |
(?DO) |
IN | over over xor if SKIP drop (DO) … body and loop end … jump PAST SKIP: drop drop drop PAST: — skips the loop when index = limit, as v3. Executed on the golden model (2026-10-03). |
(LOOP) |
IN | See below. Adds 1 and goes round again while index < limit, signed, as v3. Executed on the golden model (2026-10-03) against C on every pair of 14 loop ends, and sequences recorded from the v3 binary. |
(+LOOP) |
IN | See below. ( n -- ): adds n and goes round again while index < limit for n >= 0, while index >= limit for n < 0, as v3. Executed on the golden model (2026-10-03) against C on every pair of 14 loop ends with 11 steps, and sequences recorded from the v3 binary. Clobbers A. |
(LEAVE) |
IN | pop pop drop dup push push — FORTH-79 (ruled 2026-10-04): the limit is set equal to the index, so the loop ends at the next LOOP or +LOOP; the index is unchanged and the rest of the body still runs. v3's LEAVE left the loop at once (FORTH-83's behaviour) and, having set the index to the limit, left both on its return stack, so a LEAVE in an inner loop made the outer LOOP count the inner loop's leftovers and stop: : T 3 0 DO 10 0 DO I . I 1 = IF LEAVE THEN LOOP I . LOOP ; prints 0 1 10 in v3 and 0 1 0 0 1 1 0 1 2 here. Executed on the golden model (2026-10-04) in LOOP and +LOOP, up, down and with a step of 0. |
I |
IN | pop dup push — executed on the golden model (2026-10-03). |
J |
IN | pop pop pop dup push a! push push a — the outer index waits in A while the inner pair goes back, so J needs no return entry beyond the two loops' own (with two in-line SWAPs it needed one more). Executed on the golden model (2026-10-03), including a sequence recorded from the v3 binary. Clobbers A. |
UNLOOP |
IN | pop drop pop drop — executed on the golden model (2026-10-03) as UNLOOP EXIT, including a sequence recorded from the v3 binary. |
<less>, used by both loop ends: ( i' lim -- i' lim s ), the top bit of s set
exactly when i' < lim, signed -- i' itself when the two differ in sign, else
i' - lim (the subtraction in line):
over over xor -if SAME
drop over jump T
SAME: drop over over -
T:
(LOOP) expansion:
pop 1 + pop \ index' limit
<less> \ index' limit s
-if Lexit \ not less: done
drop push push \ R: limit index'
jump Lbody
Lexit: drop drop drop
(+LOOP) expansion: ( n -- )
dup a! pop + pop \ index' limit A: n
<less> a xor \ top bit set: go round again
-if Lexit
drop push push
jump Lbody
Lexit: drop drop drop
(LOOP) goes round again while index' < limit, signed, which is v3's rule. So 0 5 DO … LOOP runs
once, as in v3; a test for index' = limit, which this document had first, would run it round the whole
number circle. (+LOOP) goes round again exactly when s and n differ in sign: index' < limit for
n >= 0 and the opposite for n < 0. A loop costs its word two return-stack entries while it runs.
New in v4: FOR, NEXT, and UNEXT are native (§2). Counted loops that don't need an ascending
index should use them; they cost one instruction per iteration.
5.19 StarForth extensions
| Word | Fate | Notes |
|---|---|---|
ENTROPY@ ENTROPY! |
MM | Per-call-target heat table (D-6). |
WORD-ENTROPY RESET-ENTROPY TOP-WORDS |
CC | Reports over the heat registers. |
(- INIT |
CC | |
VERSION |
CC | |
SEED RANDOM |
CAP | Deterministic PRNG (xorshift) in capsule code, reproducible from the seed. |
WAIT |
CAP | Loop until the ANTICLOCK register has advanced n. |
HEARTBEAT-TICKS@ |
MM | HEARTBEAT register. |
ZUSE-AUTHENTICATE ZUSE-SESSION? ZUSE-PUBKEY@ ZUSE-CERT-INSTALLED? |
HERA |
5.20 Word-level ACL
| Word | Fate | Notes |
|---|---|---|
ACL-MODE@ ACL-MODE! ACL-TTL@ ACL-TTL! ACL-ALLOW@ ACL-ALLOW! ACL-PINNED? ACL-PIN ACL-INHERIT ACL-INIT-PRIMITIVES |
HERA | Hera holds the ACL table. In the fabric, per-message ACL checks happen in the router (§6). |
ACL-HEAT@ |
MM | |
ACL-WORD-ID |
CC |
5.21 Physics: benchmark and diagnostics
| Word | Fate | Notes |
|---|---|---|
BENCH-DICT-LOOKUP PHYSICS-CACHE-STATS PHYSICS-TOGGLE-CACHE PHYSICS-RESET-STATS PHYSICS-BUILD-INFO |
RET | Measure v3's software dictionary and hot-words cache, which do not exist on a node. |
PHYSICS-BAYESIAN-REPORT |
CAP | Host node only; kept as an analysis tool. |
PHYSICS-WORD-METRICS PHYSICS-CALC-KNOBS PHYSICS-BURN PHYSICS-SHOW-FEEDBACK |
RET | Never registered in v3. |
5.22 Physics: pipelining diagnostics
| Word | Fate | Notes |
|---|---|---|
PIPELINING-* (all six) |
RET | Return as MM reports if D-6 adds transition counters. |
5.23 Physics: freeze, heat, and decay
| Word | Fate | Notes |
|---|---|---|
FREEZE-WORD UNFREEZE-WORD FROZEN? |
MM | Freeze bit in the heat table. |
HEAT@ HEAT! |
MM | Test-only write retained. |
SHOW-HEAT ALL-HEATS |
CC | |
DECAY-RATE@ |
MM | Governor parameter register. |
FREEZE-CRITICAL |
CAP | Freezes the core set; the list is rewritten for v4 names. |
5.24 Dictionary heat optimisation
| Word | Fate | Notes |
|---|---|---|
HEAT-PERCENTILES LOOKUP-STRATEGY@ LOOKUP-STRATEGY! REORG-BUCKETS SHOW-HEAT-OPTIMIZATION COMPARE-LOOKUPS |
RET | Lookup strategy is a software-dictionary concern. The host compiler may keep a heat-ordered dictionary internally, but these words are not carried forward. |
5.25 Logging
| Word | Fate | Notes |
|---|---|---|
LOG-ERROR … LOG-DEBUG (levels) |
IN | Constants. |
LOG-LEVEL! LOG-LEVEL@ |
CAP | Variable. |
LOG-ERROR" … LOG-DEBUG" |
CC | |
LOG-*-STR |
DEV | Log-ring service on the recorder (the ARM). |
(do-log-*) |
CC | |
(LOG-APPEND-RAW) |
DEV |
5.26 Q48.16 fixed-point math
A Q48.16 value is 64 bits, so on a 32-bit node it occupies two cells and every Q word is a double word. Per D-10 it occupies two cells on a 64-bit node too. Per D-8, v4 Q values are signed.
| Word | Fate | Notes |
|---|---|---|
Q.+ Q.- |
CAP | D+, D- — executed on the golden model (2026-10-02) against v3's q48_add/q48_sub (uint64_t wrapping): bit-for-bit at 32-bit cells; at 64-bit cells the low cell is v3's, per D-10. |
Q.* |
CAP | SWAP push over over UM* drop push push over pop -if L1 SWAP jump L2 L1: SWAP drop 0 L2: pop SWAP - push push over pop UM* pop + ROT pop dup push over -if L3 drop dup jump L4 L3: drop 0 L4: push UM* pop - D+ ROT pop UM* SWAP push 0 D+ pop push over pop SWAP Q.TO-INT push Q.TO-INT pop SWAP (SWAP and ROT in line) — floor(a*b / 2^16), signed (D-8), cut to two cells. Cells 0–2 of the product come from the unsigned cell products a1*b1 (low cell), a0*b1, a1*b0, a0*b0, in that order, each input dropped after its last use and b0 waiting on the return stack. Reading a1 and b1 as signed takes (b1<0 ? a0 : 0) and (a1<0 ? b0 : 0) off cell 2; each is folded in as soon as its operands are adjacent. The result is cells 0–2 shifted right 16 with Q.TO-INT twice. Executed on the golden model (2026-10-02) against an independent limb-by-limb reference, and against v3's q48_mul for non-negative operands. Leaves its caller 3 data cells under its arguments and 3 return entries. Clobbers A. |
Q./ |
CAP | over over OR if ZERO drop push over pop SWAP over xor (Q/) 4 + b! !b -if L1 DNEGATE L1: push push -if L2 DNEGATE L2: pop pop dup if CHK drop jump DIV CHK: drop over -131072 and if CHK2 drop jump DIV CHK2: drop push push dup pop dup push N-18 FOR 2* UNEXT over over xor -if SAME drop drop -if NOOV jump OV SAME: drop push inv pop + inv -if OV NOOV: drop pop pop jump DIV OV: drop drop drop pop pop drop drop (Q/) 4 + b! @b -if OVP drop 0 MSB ; OVP: drop -1 MAXHI ; DIV: (UQ/) (Q/) 4 + b! @b -if L3 drop DNEGATE ; L3: drop ; ZERO: drop drop drop NODE-ERROR b! -1 !b over over OR if Z0 drop -if ZP drop drop 0 MSB ; ZP: drop drop -1 MAXHI ; Z0: drop ; — D-11: a * 2^16 / b, rounded toward zero; saturated to Q max / Q min on overflow; on division by zero, Q max / Q min by the dividend's sign (0 for 0 / 0) and NODE-ERROR set. The sign of the result waits in (Q/). Overflow needs no division: the quotient reaches 2^(2N-1) exactly when ` |
(UQ/) |
CAP | Internal to Q./: unsigned floor(a * 2^16 / b) for b != 0 and no overflow. Restoring division, 2N+16 steps, on one shifting register — quotient (starting as a) above remainder — with the quotient and divisor in (Q/) so the stacks carry only the remainder, the quotient bit and the loop count. Each step's quotient bit enters at the next step's shift; with no overflow the bits leaving the quotient in the last 16 steps are zeros. Trial subtraction by in-line sign tests (x - y with y on top is push inv pop + inv), including the full double subtraction with its borrow, so the loop calls nothing but D2*C. Executed on the golden model (2026-10-02). Clobbers A and B. |
D2*C |
CAP | ( lo hi cin -- lo' hi' cout ): the double shifted left one bit, cin entering at the bottom and cout the bit leaving the top (both 0 or 1). a! dup -if L1 drop 1 jump L2 L1: drop 0 L2: push over -if L3 drop 1 jump L4 L3: drop 0 L4: over + + push 2* a + pop pop — cin waits in A, and 2hi + m is over + +, so it needs one return entry. Executed on the golden model (2026-10-02). Clobbers A. |
(Q/) |
CAP | Variable, 5 cells: Q./'s quotient register, divisor and result sign. Like BASE and the hold buffer, it lives in node memory. |
Q.ABS Q.NEG |
CAP | DABS, DNEGATE — executed on the golden model (2026-10-02) against v3's q48_abs and 0 - q: bit-for-bit at 32-bit cells, including v3's wrap of Q min to itself; at 64-bit cells the low cell is v3's and the high cell is the true sign, per D-10. |
Q.COS |
CAP | v3's q48_cos_approx: on `xu = |
Q.SIN |
CAP | v3's q48_sin_approx: on r = (Q.REDUCE) x and `xu = |
(Q.REDUCE) |
CAP | ( x -- r ), v3's q48_reduce_angle: the angle reduced to one cell, -pi <= r <= pi with pi = 205887, 2pi = 411774 — the remainder of x / 2pi with the sign of x, then one step of 2pi back into range. x / 2pi does not fit a cell, but only the remainder is wanted: ` |
(QT) |
CAP | Variable, 6 cells: Q.SIN / Q.COS's sign, x^2, term, sum, n and subtract flag. |
Q.LOG |
CAP | v3's q48_log_approx: x = 2^k * m with 1.0 <= m < 2.0; ln m by up to 6 Newton rounds on e^y = m from y = m - 1.0, stopping once ` |
(QL) |
CAP | Variable, 7 cells: Q.LOG's m, y, k, rounds left, Taylor term (then delta), sum, and n * 1.0. |
Q.SQRT |
CAP | v3's q48_sqrt_approx: Newton from x0 = q/2 + 0.25, up to 8 rounds of x' = (x + q/x) / 2, returning x as soon as ` |
(QR) |
CAP | Variable, 5 cells: Q.SQRT's q, x and rounds left. |
Q.EXP |
CAP | v3's q48_exp_approx: 1 + x + x^2/2! + … to 10 terms on `x = |
(QE) |
CAP | Variable, 8 cells: Q.EXP's x, term, sum, sign and term index. |
Q.FROM-INT |
CAP | push 0 a! 0 pop 15 FOR +* UNEXT push drop a pop — n * 2^16 as a signed double. +* with S = 0 never adds, so each step is an exact arithmetic right shift of T:A; starting from T:A = n:0 (that is, n * 2^N) and shifting N-16 bits leaves n * 2^16. The count is N-17: 15 at 32-bit cells, 47 at 64. Negative values are no longer clamped to 0. Executed on the golden model (2026-10-02): matches v3's q48_from_u64 for n >= 0 (at 64-bit cells in the low cell, where v3 wraps for n >= 2^47, per D-10). Clobbers A. |
Q.TO-INT |
CAP | push a! 0 pop 15 FOR +* UNEXT drop drop a — the same +* shift, 16 bits at every cell width, leaving the low cell in A. Rounds toward minus infinity, as v3's arithmetic shift does (−1.5 gives −2). Executed on the golden model (2026-10-02): v3's q48_to_u64 exactly at 64-bit cells, its low cell at 32. Clobbers A. |
Q.1 Q.0 Q.SCALE |
IN | Double-cell constants, placed in line as two literals, low cell first: Q.1 and Q.SCALE are 65536 0 (1.0), Q.0 is 0 0. v3's are 65536, 0 and 65536. Executed on the golden model (2026-10-03): the values match v3's, Q.1 Q.TO-INT is 1, 1 Q.FROM-INT is Q.1, and q Q.1 Q.* and q Q.0 Q.+ return q. |
Q.= Q.< Q.> Q.0= Q.MAX Q.MIN |
CAP | D=, D<, 2SWAP D< (for Q.>), D0=, DMAX, DMIN. Signed (D-8); v3 compared unsigned, so v3 agrees only when both values have the same sign. Executed on the golden model (2026-10-02). Q.> was given here as SWAP D<, which swaps single cells, not Q values, and gave a wrong answer in 11702 of 20169 test cases. |
Q.PRINT |
CAP | ( q -- ): the integer part, a point, the five digits floor(frac * 100000 / 65536), then one space, as v3; always decimal, whatever BASE is, and BASE is put back. Signed (D-8): a negative value prints - and its magnitude (−1.5 prints -1.50000), where v3 printed it as a large unsigned number. dup (QP) 3 + b! !b -if A DNEGATE A: (QP) 2 + b! !b dup (QP) 1 + b! !b BASE b! @b (QP) b! !b 10 BASE b! !b 65535 and 4 FOR dup 2* 2* + UNEXT 10 FOR 2/ UNEXT 0 <# # # # # # drop drop 46 HOLD (QP) 1 + b! @b (QP) 2 + b! @b dup push push a! 0 pop 15 FOR +* UNEXT drop drop a pop 15 FOR 2/ UNEXT HIMASK and #S (QP) 3 + b! @b SIGN #> (QP) b! @b BASE b! !b jump OUT — OUT is the TYPE jump SPACE at the end of .R. The fraction is frac * 3125 / 2048, which stays inside a 32-bit cell where frac * 100000 would not; times 3125 is times 5 five times. The integer part is ` |
(QP) |
CAP | Variable, 4 cells: Q.PRINT's saved BASE, ` |
5.27 Inference engine
The runtime governor moves into hardware as the multi-level Rolling Window of Truth (see
JUSTIFICATION.md). These words survive on the host node as analysis tools.
| Word | Fate | Notes |
|---|---|---|
INFER-RUN INFER-WINDOW@ INFER-DECAY@ INFER-VARIANCE@ INFER-FIT@ INFER-EARLY-EXIT@ |
CAP | Host node only. Ported from inference_words.c. |
Q.VARIANCE INFER-DECAY-SLOPE INFER-WINDOW-WIDTH WINDOW-DIVERSITY |
CAP | Host node only. |
L8-MODE L8-UPDATE L8-APPLY L8-TABLE-FORCE |
MM | Jacquard selector state becomes governor registers. L8-TABLE-FORCE remains the DoE override. |
BAYES-* (all six) |
RET | Model the v3 hot-words cache and bucket search. |
5.28 DEFER and IS
| Word | Fate | Notes |
|---|---|---|
DEFER IS DEFER@ |
CC | A deferred word's runtime is @p push ; followed by the stored xt. |
5.29 – 5.32 Framebuffer, keyboard, TrueType, scrollback
| Word | Fate |
|---|---|
PLOT FB-WIDTH FB-HEIGHT |
DEV |
KBD-SCAN KBD-DEBUG VKBD-EVENT VKBD-DEBUG KEY-EVENT ALT+TAB |
DEV |
TTF-TEXT |
DEV |
SCROLL-BACK SCROLL-FWD |
DEV |
5.33 Kernel REPL and DoE hooks
| Word | Fate | Notes |
|---|---|---|
HB-ON HB-OFF |
MM | Recorder-enable register. The fabric pushes DoE rows into a FIFO; the ARM drains it to storage. |
BLK-ATTACH-ACK KH-BLK-ATTACH-SEND KH-ELEVATE-SEND |
RET | Kernel-Hermes calls. Hermes is now the fabric; these become ordinary packets (§6). |
5.34 Hera and child-VM words
| Word | Fate | Notes |
|---|---|---|
BIRTH KILL START STOP USE EXEC EJECT CONNECT-HERMES CONNECT-ARTEMIS BYE |
HERA | A VM becomes a node (or a group of nodes). BIRTH loads a capsule into a node and releases it. |
VM-EXEC VM-CALL VM-STEP |
CAP | Built on SEND and RECV (§6). |
VM-HEAT VM-ERROR? VM-COUNT VM-CONSERVED? VM-PHYSICS-STATUS |
MM + HERA | Per-node heat is a register; fleet totals are Hera's. |
SWITCH-MARK-WORK |
RET | Nodes run concurrently; there is no context switch to mark. |
MAMA-VM-ID NAME>XT |
HERA, CC | |
CAPSULE-* WORKER-BIRTH UNATTENDED-BIRTH CONSOLE-ATTACH |
HERA | |
MINT MINT-SCRATCH MINT-SCRATCH-EMIT ZUSE-ELIGIBILITY-ADD ZUSE-ELIGIBLE? ELEVATE-PUBKEY-UNPACK |
HERA | |
RUNCAP-TEST PAIR-TEST |
HERA | Diagnostics, kept. |
STADIUM-ADMIT STADIUM-EVICT STADIUM-RES-PULL STADIUM-RES-PUSH |
HERA | Admission and reservoir are Hera's. |
STADIUM-HEAT@ STADIUM-HEAT! STADIUM-RES@ STADIUM-WORD-HEAT |
MM |
5.35 Hosted lifecycle stubs
| Word | Fate |
|---|---|
BIRTH KILL PAUSE RESUME USE (hosted) |
RET — the hosted v4 golden model implements the real HERA messages. |
6. Messaging between nodes
Each node has four neighbour ports (PORT-UP, PORT-DOWN, PORT-LEFT, PORT-RIGHT) mapped into its
address space. A read from a port blocks until the neighbour writes; a write blocks until the neighbour
reads. This is the GA144 model. No instruction is added: a port is an address.
6.1 Packet format (proposal)
| Word | Contents |
|---|---|
| 0 | Header: destination node (8 bits), type (8), TTL/heat (8), payload length in words (8). |
| 1 | ACL tag: sender identity fingerprint, checked by the router on every packet. |
| 2 … | Payload. |
The router in each node forwards packets not addressed to it, decrements TTL, and drops a packet whose
TTL reaches zero or whose ACL tag fails. This is v3 Hermes's per-message ACL check and unconditional TTL
expiry, moved into logic. A packet type SOS is reserved for any node to emit.
6.2 Send and receive
: SEND ( addr u port -- ) b! SWAP a! BEGIN dup WHILE 1- @+ !b REPEAT drop ;
: RECV ( addr u port -- ) b! SWAP a! BEGIN dup WHILE 1- @b !+ REPEAT drop ;
A walks the buffer and B holds the port, so each word moved costs one fetch and one store.
7. Memory-mapped registers
Addresses are assigned in the node memory map (D-4). Names only here.
| Register | Access | Contents |
|---|---|---|
HEAT-OP[0..31] |
R | Per-opcode retirement heat. |
HEAT-CALL[...] |
R/W | Per-call-target heat, with freeze bit (D-6). |
ANTICLOCK |
R | Virtual tick: a pure function of the execution stream. |
HEARTBEAT |
R | Adaptive heartbeat count. |
GOV-* |
R/W | Governor parameters and Jacquard selector state (L8-*, decay rate). |
REC-ENABLE |
R/W | DoE recorder on/off (HB-ON / HB-OFF). |
PORT-STATUS |
R | Per-port ready flags, for non-blocking polls. |
NODE-ERROR |
R/W | Arithmetic error flag: set to −1 by Q./ on division by zero (D-11), by Q.SQRT / Q.LOG outside their domain (D-12) and by HOLD on a bad character or a full buffer (D-13), cleared by writing 0. VM-ERROR? reads it. |
CONSOLE-TX |
W | Console transmit: a store sends the low 8 bits of the value as one character. This is what EMIT writes to. On the single-node golden model it is a capture register: the character is appended to a buffer on the node and memory is not written (v4_node_console_attach, v4/include/v4/node.h), so printing words can be run and their output compared with v3's. With the mesh (step 2) it becomes the port to the console node. |
CONSOLE-RX |
R | Console receive: a fetch gives the next pending character, 0–255, and takes it; with none pending it gives −1 and takes nothing. This is what KEY reads. On the single-node golden model the characters come from a queue the test feeds (v4_node_console_input_attach, v4_node_console_feed, v4/include/v4/node.h). Only the data fetches @, @+ and @b see the register; instruction words and literals at the same address are read as memory. With the mesh (step 2) it becomes the port from the console node, and a read with nothing pending will block instead. |
CONSOLE-STATUS |
R | Console receive status: a fetch gives −1 when a character is pending and 0 when not, and changes nothing. ?TERMINAL reads it, and KEY polls it. On the mesh it is the console port's bit of PORT-STATUS. |
DSTACK-DEPTH |
R/W | A fetch gives how many values the data stack holds, the fetch's own push not counted. A store empties the data stack; the value stored is taken off first and ignored. DEPTH reads it; ABORT stores to it (D-16). |
RSTACK-DEPTH |
R/W | The same for the return stack. QUIT, ABORT and the error exits store to it before they call anything. |