Author SHA1 Message Date
rajamesandClaude Opus 5.5 861f800b7f feat(v4.0.0): KEY and ?TERMINAL on the console registers
?TERMINAL reads CONSOLE-STATUS; KEY polls it until a character is
pending and then takes it from CONSOLE-RX.  Executed on the golden
model at both cell widths, including a transcript of the v3 binary, and
KEY shown still waiting after 5000 instruction words with no input.

v3's KEY returned -1 at the end of its input; here KEY waits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 20:53:56 -04:00
rajamesandClaude Opus 5.5 2c16183788 feat(v4.0.0): CONSOLE-RX and CONSOLE-STATUS on the single-node model
The console's receive side, standing in for the console node until the
mesh exists, as CONSOLE-TX does for output.  A data fetch (@, @+, @b)
from CONSOLE-STATUS gives -1 when a character is pending and 0 when
not; from CONSOLE-RX it gives the next character and takes it, or -1
with none pending.  The characters come from a queue the test feeds.

Instruction words and literals are still fetched with v4_node_load, so
code at a register's address is never taken for the register.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 20:53:56 -04:00
rajamesandClaude Opus 5.5 03d8d991ef feat(v4.0.0): the DO loop runtimes
(DO) (?DO) (LOOP) (+LOOP) (LEAVE) I J UNLOOP and (0BRANCH), laid down
in line by hand as the compiler will, executed on the golden model at
both cell widths against C on every pair of 14 loop ends and 11 steps,
and against sequences recorded from the v3 binary.

(LOOP) goes round again while index < limit, signed, as v3 does; the
document's equality test differed whenever start >= limit.  (+LOOP) is
now specified.  J keeps the outer index in A and needs no extra return
entry.

LEAVE discards the limit and index.  v3 left them on its return stack,
which made a LEAVE in an inner loop stop the outer one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 20:13:24 -04:00
rajamesandClaude Opus 5.5 481d484e93 feat(v4.0.0): the mixed and double leftovers
M- M* M/MOD MOD */ */MOD, D0< D2* D2/ 2ROT, 2DROP and 2>R 2R@ 2R>,
beside UM* and SM/REM in test_foundation.c.  Executed on the golden
model at both cell widths against C and results recorded from the v3
binary.

M- widens n before negating it, so the most negative n is right.
*/MOD goes through a full double product.  D2/ is one +* step.
M- and M/MOD take the double in the standard order ( lo hi ), as M+
does; v3 took its low cell on top.

The foundation test's node is now full: 958 of the 960 words below its
variables.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 18:37:53 -04:00
rajamesandClaude Opus 5.5 84c7711763 feat(v4.0.0): the small single-cell words
NIP SWAP ROT -ROT ?DUP 2DUP, >R R@ R>, @ ! +! -! 2@ 2! CELLS,
- NEGATE 1+ 1- 2+ 2- MIN MAX, OR NOT 0= 0< 0<> 0> = <> < > <= >= U<
WITHIN TRUE FALSE in a new test, and * and / beside UM* and /MOD in
test_foundation.c.  Executed on the golden model at both cell widths
against C on every combination of the edge values and against recorded
transcripts of the v3 binary.

WITHIN is low <= n < high with signed comparisons, which is what v3
computes; the document's circular form differed for low > high.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 17:49:30 -04:00
rajamesandClaude Opus 5.5 cb66db1107 feat(v4.0.0): the byte-string words
FILL ERASE MOVE, COUNT CMOVE CMOVE> BLANK -TRAILING COMPARE SEARCH SCAN
SKIP, as loops over the call-free C@ and C!, executed on the golden
model at both cell widths against C and five recorded transcripts of
the v3 binary.

A negative count reads as 0 where v3 read it so, and elsewhere writes
nothing and sets NODE-ERROR.  v3's counted-string auto-detection is not
kept in any of them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 17:25:27 -04:00
rajamesandClaude Opus 5.5 7c5be22799 feat(v4.0.0): C@ is call-free
One case per byte position, like C!: the cell is shifted down with a
2/ loop and masked.  It replaces the version built on LSHIFT, RSHIFT
and SWAP calls.  C@ now leaves its caller 7 return entries (was 4), and
the words above it gain with it: TYPE 6 (was 3), DUMP 4, Q.PRINT, U.
and U.R 3 (were 2).  The signed number words stay at 2: their sign
waits on the return stack.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 17:15:57 -04:00
rajamesandClaude Opus 5.5 e50cc1f73a feat(v4.0.0): BASE, DECIMAL, HEX, OCTAL and HLD
BASE and HLD leave their variable's word address; DECIMAL, HEX and
OCTAL store 10, 16 and 8.  Executed on the golden model at both cell
widths, including a recorded transcript of the v3 binary.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 16:46:38 -04:00
rajamesandClaude Opus 5.5 030ca63639 feat(v4.0.0): ?, Q.PRINT and DUMP
Built on the number-output words and executed on the golden model at
both cell widths against a C reference and recorded transcripts of the
v3 binary (DUMP byte for byte at 64-bit cells).

Q.PRINT is signed (D-8) and always decimal; DUMP is always hex; both
put BASE back.  Each keeps its working state in a variable, (QP) and
(DP), so each leaves its caller 4 data cells and 2 return entries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 16:33:19 -04:00
rajamesandClaude Opus 5.5 13335dcc9c feat(v4.0.0): . U. D. .R U.R D.R and SPACES
The number-printing words, on pictured output and TYPE, executed on the
golden model at both cell widths against a C reference (ten bases,
eleven field widths) and six recorded transcripts of the v3 binary.

Each plain word is its .R word with a width of 0; all six share one
tail.  The field width waits in a variable, (W), so the picture runs no
deeper than it does on its own: each word leaves its caller 4 data
cells and 2 return entries.

As v3, the .R words print a trailing space.  Unlike v3, D. takes
( lo hi ), prints the whole double, and printing honours BASE.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 15:51:27 -04:00
rajamesandClaude Opus 5.5 40178098e9 feat(v4.0.0): EMIT, CR, SPACE and TYPE on the console register
: EMIT  ( c -- )        CONSOLE-TX b! !b ;
  : CR    ( -- )          10 jump EMIT
  : SPACE ( -- )          32 jump EMIT
  : TYPE  ( baddr u -- )
    -if OK  drop drop  NODE-ERROR b! -1 !b ;
    OK: if DONE  over C@ EMIT  push 1 + pop  -1 +  jump OK
    DONE: drop drop ;

They follow v3 (v3/src/word_source/io_words.c): EMIT prints the low byte
of the cell, CR character 10, SPACE a blank; TYPE prints u bytes,
nothing for u = 0, and for u < 0 nothing, with NODE-ERROR set where v3
raised its error flag. TYPE does not check the address range, which v3
does; out-of-range addressing is still an open question in node.h.

New test v4/tests/test_terminal.c, on a node of its own with the console
attached, 939 checks per width. Two transcripts of the real v3 binary
are recorded as expected output:
  65 EMIT 66 EMIT SPACE 67 EMIT CR 68 EMIT 321 EMIT  ->  "AB C\nDA"
  S" Hello, v3" TYPE 91 EMIT <text> 0 TYPE 93 EMIT   ->  "Hello, v3[]"
Also: EMIT of every byte and of wider values, CR, SPACE, TYPE from every
start within a cell at lengths 0..40, bytes with the top bit set and
zero bytes, negative counts, and that TYPE writes no memory. At 32- and
64-bit cells, optimised and ASan+UBSan (`make test`, `make sanitize`).
Five mutations each fail.

Headroom (data cells under args / return entries): EMIT 8/8, TYPE 4/3.
TYPE's depth is C@'s, which is still as written (C@ -> RSHIFT -> SWAP).

Test results on the amd64 host only. This is a development check, not
acceptance (JUSTIFICATION.md section 16).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 12:13:25 -04:00
rajamesandClaude Opus 5.5 7e3932a4ad feat(v4.0.0): CONSOLE-TX capture register on the single-node model
EMIT is a device service: a character sent to the console node. The
mesh and its ports are development step 2 and the memory map is open
(D-4), so the single-node model now stands in for the console with one
memory-mapped register, so that printing words can be run and their
output compared with v3's.

v4_node_console_attach(n, addr): after it, a store to word address
`addr` appends the low 8 bits of the value to a buffer on the node
(V4_CONSOLE_CAP characters, 4096 by default) and does not write memory;
a load from `addr` reads the memory word as before. Characters past the
capacity are counted in console_dropped and discarded. The hook is in
v4_node_store, which all four store opcodes use.

It is off by default: v4_node_reset detaches the console (address -1)
and empties the capture, so the ISA's behaviour and every existing test
are unchanged unless a console is attached. The address is the
caller's choice.

New test v4/tests/test_console.c, 22 checks per width: direct stores,
!b, ! and !+ through the executor printing "Hi!", memory at and around
the register untouched, the low byte only, overflow, detach, a console
at word 0, and reset. At 32- and 64-bit cells, optimised and ASan+UBSan
(`make test`, `make sanitize`); all other v4 tests still pass. Four
mutations of the hook each fail.

DECOMPOSITION.md section 7 gains the CONSOLE-TX row. No printing word is
defined here; EMIT and the words on it are still to do.

Test results on the amd64 host only. This is a development check, not
acceptance (JUSTIFICATION.md section 16).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 11:57:49 -04:00
rajamesandClaude Opus 5.5 7a21f07ad5 feat(v4.0.0): # keeps its high quotient on the data stack
`#` held the high quotient on the return stack across its second
UM/MOD, which was the deepest point of pictured output once C! was made
call-free. It now rotates it under the division on the data stack
(-ROT in line: SWAP push SWAP pop).

Headroom, return entries: <# #S #> 2 -> 3, the signed picture 1 -> 2.
Data room is unchanged.

Same 4716 checks per width at 32- and 64-bit cells, optimised and
ASan+UBSan (`make test`, `make sanitize`); the headroom checks now
require the new figures.

Test results on the amd64 host only. This is a development check, not
acceptance (JUSTIFICATION.md section 16).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 11:51:03 -04:00
rajamesandClaude Opus 5.5 638aeb6636 feat(v4.0.0): call-free C!
C! as written in 5.3 called RSHIFT and LSHIFT (which call SWAP) and OR.
It is now one straight-line case per byte position: the byte is shifted
up with a `2* unext` loop, that byte of the cell cleared with a constant
mask, and the two added. The word address is `2/ 2/` of the byte
address. The upper half of a 64-bit cell is still preserved.

Headroom (data cells under args / return entries): 5/4 -> 7/7.

Same checks as before (every byte of two adjacent words, eight values,
neighbours and the upper half untouched) at 32- and 64-bit cells,
optimised and ASan+UBSan (`make test`, `make sanitize`). Four mutations
fail, one of them only at 64-bit cells, where it differs.

test_pictured.c carries the same C!. The pictured words did not gain
return-stack room from this: <# #S #> still leaves 2 entries and the
signed picture 1. With C! shallow, the deepest point is now inside `#`,
which holds the high quotient on the return stack across its second
UM/MOD. Recorded in 5.8; `#` is unchanged.

Test results on the amd64 host only. This is a development check, not
acceptance (JUSTIFICATION.md section 16).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 11:45:52 -04:00
rajamesandClaude Opus 5.5 6a019b8968 feat(v4.0.0): pictured output <# # #S HOLD SIGN #>; D-13
DECOMPOSITION.md 5.8 only named these as "standard pictured-output
definitions over UM/MOD and a hold buffer". They are now written out
(5.8) and run on the golden model.

D-13 (ruled 2026-10-03): the hold buffer takes 63 characters, as v3's;
HOLD of a value outside 0-255, or into a full buffer, stores nothing and
sets NODE-ERROR, which is what v3 did with its error flag.

Behaviour follows v3 otherwise: digits 0-9 then A-Z, BASE outside 2..36
reads as 10, SIGN ( n -- ). Stack effects are the standard ones, as 5.8
already ruled (v3 took its double low cell on top). The buffer is 64
bytes, filled backwards from its end through HLD; `#` divides by the
base in two UM/MOD steps. HOLD, SIGN and # end in a jump to the next
word instead of a call, which saves two return-stack entries: with
calls, the signed picture `.` needs overflowed the 9-deep return stack.

New test file v4/tests/test_pictured.c, on a node of its own:
test_foundation.c's hand-assembled words already fill 901 of a node's
1024 words. It re-assembles SWAP, OR, UM/MOD, LSHIFT, RSHIFT and C! as
they are in test_foundation.c.

Checked against a C reference (short division on 16-bit limbs) at 32-
and 64-bit cells, optimised and ASan+UBSan (`make test`, `make
sanitize`), 4716 checks per width: <# #S #>, <# # # 46 HOLD #S #> and
the signed picture, in bases 10, 16, 2, 8, 36, 3 and the invalid 0, 1,
37, -5, on every pair of 15 edge cells; HOLD of 12 values; and a base-2
number that overflows the buffer (63 characters kept, NODE-ERROR set,
nothing outside the buffer written). Six mutations all fail.

Headroom: <# #S #> leaves 3 data cells and 2 return entries; the signed
picture leaves 1 return entry. The depth is C!'s as written (C! ->
LSHIFT -> SWAP), which is unchanged.

Test results on the amd64 host only. This is a development check, not
acceptance (JUSTIFICATION.md section 16).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 11:37:08 -04:00
rajamesandClaude Opus 5.5 2e4d1330ea test(v4.0.0): execute C@ and C! on the golden model
Both run exactly as written in DECOMPOSITION.md 5.3 and need no change:
four bytes to a cell, little-endian, byte address = 4 * word address +
byte index, at either cell width.

Checked at 32- and 64-bit cells, optimised and ASan+UBSan (`make test`,
`make sanitize`): every byte of two adjacent words, eight values
including ones wider than a byte, with the other bytes of the word, the
upper half of a 64-bit cell and the neighbouring words left untouched.
Breaking C!'s mask or C@'s mask fails.

Headroom (data under args / return): C@ 6/4, C! 5/4.

Test results on the amd64 host only. This is a development check, not
acceptance (JUSTIFICATION.md section 16).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 11:16:16 -04:00
rajamesandClaude Opus 5.5 f030edcd08 test(v4.0.0): execute LSHIFT and RSHIFT on the golden model
Both run exactly as written in DECOMPOSITION.md 5.5 and need no change.
Checked against C on the edge vectors for every count 0 .. N (a count
of N gives 0), at 32- and 64-bit cells, optimised and ASan+UBSan
(`make test`, `make sanitize`). Removing RSHIFT's sign-bit mask fails.

Headroom (data under args / return): 7/5 each.

They are dependencies of C@ and C!, which the pictured-output hold
buffer needs.

Test results on the amd64 host only. This is a development check, not
acceptance (JUSTIFICATION.md section 16).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 11:10:03 -04:00
rajamesandClaude Opus 5.5 13ec4b1b6c test(v4.0.0): execute the in-line constants Q.1, Q.0 and Q.SCALE
DECOMPOSITION.md 5.26 gives them fate IN: the compiler places two
literals, low cell first. Q.1 and Q.SCALE are `65536 0`, Q.0 is `0 0`;
v3's are 65536, 0 and 65536.

Each expansion is assembled and run, and checked against v3's value;
then in use: 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 for 2000 pseudo-random q plus 0, Q max and Q min.
At 32- and 64-bit cells, optimised and ASan+UBSan (`make test`,
`make sanitize`). A wrong value in any of the three fails.

Test results on the amd64 host only. This is a development check, not
acceptance (JUSTIFICATION.md section 16).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 11:03:57 -04:00
rajamesandClaude Opus 5.5 8235078b3b feat(v4.0.0): Q.COS, bit for bit with v3
Q.COS is v3's q48_cos_approx: on |(Q.REDUCE) x|, the even Taylor series
to n = 10. It sets term and sum to 1.0, n to 2 and the sign cell to 0,
then jumps into Q.SIN's loop, so it adds no call level.

Checked bit for bit against v3's q48_cos_approx (ported into the test)
on the same 33 edge angles and 3000 pseudo-random angles as Q.SIN, at
32- and 64-bit cells, optimised and ASan+UBSan (`make test`,
`make sanitize`). Mutating the start index, the start sum, the
alternation or the sign cell each fails 2500+ cosine checks and no sine
checks.

Headroom (data under arg / return): Q.COS 3/2.

Test results on the amd64 host only. This is a development check, not
acceptance (JUSTIFICATION.md section 16).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 10:53:52 -04:00
rajamesandClaude Opus 5.5 1ce705783c feat(v4.0.0): Q.SIN and (Q.REDUCE), bit for bit with v3
(Q.REDUCE) is v3's q48_reduce_angle: the angle as one cell in [-pi, pi].
x / 2pi does not fit a cell, but only the remainder is needed, and
|x| mod 2pi is two UM/MOD steps (high cell first, its remainder leading
the low cell); then the sign of x and one step of 2pi back into range.

Q.SIN is v3's q48_sin_approx on the reduced angle: the odd Taylor series
to n = 11, each term the last times x^2 over n(n-1), stopping below 10
ulp. After the reduction every value fits one cell, so the 6-cell
variable (QT) holds single cells. The loop is laid out for Q.COS to jump
into, so the pair share it without an extra call level.

Checked bit for bit against v3 (both functions ported into the test) on
33 edge angles -- around +-pi/2, +-pi, +-2pi, the 32-bit seam and both
64-bit extremes -- and 3000 pseudo-random angles of every size and sign,
at 32- and 64-bit cells, optimised and ASan+UBSan (`make test`,
`make sanitize`). Mutating either range boundary, the series length or
the sign alternation fails. Moving the 10-ulp stop to 11 fails nothing,
and a scan of all 205888 reduced angles in C shows why: no input's
result depends on it, for sine or cosine.

Headroom (data under arg / return): (Q.REDUCE) 5/5, Q.SIN 3/2.

Test results on the amd64 host only. This is a development check, not
acceptance (JUSTIFICATION.md section 16).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 10:44:42 -04:00
rajamesandClaude Opus 5.5 e117333a87 feat(v4.0.0): Q.LOG, flattened, bit for bit with v3
Q.LOG is 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, result y + k * ln 2. As ruled, e^y is
Q.EXP's Taylor loop written in line rather than a call to Q.EXP, so
Q.LOG calls only Q.*, Q./ and UM*. After the reduction m, y, the Taylor
term and its sum each fit one cell, so the 7-cell variable (QL) holds
single cells. x <= 0 returns 0 and sets NODE-ERROR (D-12).

Checked bit for bit against v3's q48_log_approx (ported into the test)
on 22 positive edge values, 2000 pseudo-random x of every magnitude and
a sweep of every 17th reduced m, at 32- and 64-bit cells, optimised and
ASan+UBSan (`make test`, `make sanitize`). Mutating the round count, the
100-ulp stop, ln 2 or the Taylor length all fail.

v3's clamp y = max(y - corr, 0) is kept but cannot fire: a scan of all
65536 values of m in C never reaches it, and never needs more than 4
rounds. So no test covers it.

The test harness's per-call step limit is raised from 100000 to 4000000
instruction words: a 64-bit Q./ takes about 6500, and a mutant forcing
extra Newton rounds ran past the old limit and looked like a width
difference.

Headroom (data under arg / return): Q.LOG 3/2.

Test results on the amd64 host only. This is a development check, not
acceptance (JUSTIFICATION.md section 16).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 10:29:59 -04:00
rajamesandClaude Opus 5.5 a2a4a85d38 docs(v4.0.0): record the three products and v4's acceptance criteria
Both ruled by Captain Bob, 2026-10-03.

Products (README.md, JUSTIFICATION.md section 15): Hosted StarForth F18
(native Linux, amd64/arm64/riscv64, on hardware); FPGA StarForth F18 (a
32-bit build loaded into the FPGA, the gateway and foundation); and
StarshipOS (bare metal, LithosAnanke on the v4 F18 engine). FORTH-79
recomposed on the F18 engine is stored as a capsule, as are the
StarshipOS portions; the tree will be reorganised around this.

Acceptance (JUSTIFICATION.md section 16, v4/README.md): v4 is equivalent
to v3 at any point in time -- same vocabularies, same behaviour, on the
F18-derived engine -- and every ISA, hosted and bare metal, must still
reach its ok prompt. `make -C v4 test` is a development check, not
acceptance. v4 had no acceptance criteria before this.

Documentation only; no code changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 10:13:58 -04:00
rajamesandClaude Opus 5.5 f4bfe7de02 logs: three-architecture kernel acceptance baseline at a2d21d5c
Baseline run for StarForth-v4.0.0 as it stands, before any further work.
`make -f kernel/Makefile ARCH=<arch> clean qemu` for amd64, aarch64 and
riscv64, one at a time, disk/artemis.img and
disk/thumbdrives/zuse-thumb-ident.img restored to their committed state
before each.

All three reach `[zuse@Hera] ok>`, with zero UNKNOWN WORD, PARITY:OK,
1050 POST tests run and ALL IMPLEMENTED TESTS PASSED,
stadium_conserved(Artemis)=true.

dict_hash is identical across the three:
  Hera (PARITY:M7.1a, word_count=530)  0x08873e0f44b7cb2a
  dict_hash                            0xe11082140cf86b05
  dict_hash                            0xb1256603f848e2b9
  dict_hash                            0xc791409ac1715690

QEMU was asked to quit over QMP once the prompt had appeared and the
serial log had been quiet for 10 seconds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 10:11:25 -04:00
rajamesandClaude Opus 5.5 a2d21d5cda feat(v4.0.0): Q.SQRT bit for bit with v3; D-12 domain errors
D-12 (ruled 2026-10-02): Q.SQRT of a negative value and Q.LOG of zero or
a negative value return 0 and set NODE-ERROR, as Q./ does on division by
zero.

Q.SQRT is v3's q48_sqrt_approx: Newton from x0 = q/2 + 0.25, up to 8
rounds of x' = (x + q/x) / 2, returning x once |x' - x| < 10 ulp;
sqrt(0) = 0 and sqrt(1.0) = 1.0. q, x and the rounds left live in a
5-cell variable (QR). Checked bit for bit against v3's q48_sqrt_approx
(ported into the test) on 19 edge values and 3000 pseudo-random q below
2^48, at both cell widths, optimised and ASan+UBSan. Above 2^48 v3's
q48_div saturates and v4 divides correctly, so there is no parity there.

Bug found and fixed on the way: the "< 10 ulp" test subtracted 10 from
the step's low cell as a signed number, so at 32-bit cells a step of
2^31 or more read as small and the iteration stopped early. It now
tests the low cell's top bit first, as Q.EXP does. The first test run
missed it because `make build/32-test_foundation.c` rebuilds nothing
(the Makefile's rules use absolute paths); the mutation runs, compiled
from source, exposed it. Only `make test` is used from here.

Headroom (data under arg / return): Q.SQRT 3/2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 09:07:30 -04:00
rajamesandClaude Opus 5.5 29cf2688d2 feat(v4.0.0): Q.EXP bit for bit with v3; lighter D2*C and (UQ/)
Q.EXP is v3's q48_exp_approx: the Taylor series to 10 terms on |q|,
stopping below 50 ulp, 1.0 Q./ e^|q| for q < 0, e^0 = 1.0, and for
|q| >= 16.0 either 0 or Q max (v3 returned all ones, which is -ulp read
signed under D-8). x, term, sum, sign and term index live in a new
8-cell variable (QE). Checked bit for bit against v3's q48_exp_approx
(ported into the test) on 23 edge values and 3000 pseudo-random q in
(-16.0, 16.0), at both cell widths, optimised and ASan+UBSan; dropping a
term, moving the 50-ulp stop or skipping the reciprocal all fail.

Q.EXP calls Q./, which calls (UQ/), which calls D2*C, and at first it
left its caller no return entries. Lightened along the chain:
- D2*C takes cin in A and builds 2hi+m with `over + +`: no ROT, no SWAP.
- (UQ/) parks the quotient bit instead of using ROT, stores q without
  SWAP, and does its full trial subtraction (with borrow) in line
  instead of calling DNEGATE D+.
- Q.EXP keeps its term index in (QE) instead of a FOR count.

Headroom (data under args / return): (UQ/) 4/3 -> 4/4, Q./ 3/2 -> 3/3,
Q.EXP 3/2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 00:26:36 -04:00
rajamesandClaude Opus 5.5 9e03bdab19 feat(v4.0.0): Q./ under D-11, with (UQ/), D2*C and the (Q/) variable
D-11 (ruled 2026-10-02): Q./ rounds toward zero, saturates to Q max /
Q min on overflow, and on division by zero returns Q max / Q min by the
dividend's sign (0 for 0/0) and sets the new NODE-ERROR register (7),
which VM-ERROR? reads.

- (UQ/): unsigned floor(a * 2^16 / b) for b != 0 and no overflow.
  Restoring division, 2N+16 steps, on one shifting register (quotient
  above remainder). Quotient register and divisor live in a 5-cell
  variable (Q/), like BASE and the hold buffer, so the stacks carry only
  the remainder, the quotient bit and the loop count. A first version
  kept the divisor on the return stack and overflowed it when called
  from Q./ (it never returned).
- D2*C: double shift left with carry in and out.
- Q./: zero-divisor handling, signs (kept in (Q/)), an overflow test
  that needs no division (|a| * 2^16 >= |b| * 2^(2N-1), possible only
  for |b| < 2^17), then (UQ/) and the sign.

Checked at 32- and 64-bit cells, optimised and ASan+UBSan, against an
independent limb-by-limb long division (including NODE-ERROR): every
pair of 18 edge Q values, 13 width-native edge values (true Q max/min at
either width), the overflow boundary, divisors in [2^(2N-2), 2^(2N-1)),
and pseudo-random cases; and against v3's q48_div for non-negative
a < 2^48. Q.* now also runs on the width-native edge values.
Mutations of the compare paths, the take path, the error flag and the
overflow mask are all caught (the mask matters only for Q min / -1.0).

Headroom (data under args / return): Q./ 3/2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 00:17:11 -04:00
rajamesandClaude Opus 5.5 8f37e15f8a feat(v4.0.0): repack Q.* so no UM* call holds all four inputs
Q.* now forms its products in the order a1*b1 (low cell), a0*b1, a1*b0,
a0*b0, dropping each input after its last use, with b0 waiting on the
return stack. Each sign correction, (b1<0 ? a0 : 0) and (a1<0 ? b0 : 0),
is folded into cell 2 as soon as its operands are adjacent, using -if
rather than 0< calls. At most four live values sit under any UM* call.

Headroom (data cells under args / return entries): 2/2 -> 3/3.

Same checks as before at 32- and 64-bit cells, optimised and ASan+UBSan;
dropping either sign correction fails about 9900 checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 23:33:57 -04:00
rajamesandClaude Opus 5.5 54e1cdafb1 feat(v4.0.0): UM* corrections after the loop; return headroom 4 -> 6
UM* parked its three corrections (c_hi, c_lo, t0) on the return stack
before the +* loop, leaving its caller 4 return entries. Now u1 and u2
wait on the data stack under the loop -- +* touches only T, S and A --
and the corrections are made after it, with -if in place of 0< calls.
The return stack holds the loop count, then at most two temporaries.
The loop count is pushed before s is made, so the data stack never holds
more than four cells: data headroom is unchanged at 6.

Headroom (data cells under args / return entries): 6/4 -> 6/6.
Q.*, which calls UM* four times, goes from 2/1 to 2/2.

Same checks as before at 32- and 64-bit cells, optimised and
ASan+UBSan; breaking any of the four corrections fails 4900+ checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 23:31:52 -04:00
rajamesandClaude Opus 5.5 ab3b708540 feat(v4.0.0): Q.* from four UM* cell products
Q.* is floor(a*b / 2^16), signed (D-8), cut to two cells. Cells 0..2 of
the product come from the unsigned cell products a0*b0, a0*b1, a1*b0 and
the low cell of a1*b1; reading a1 and b1 as signed takes
(a1<0 ? b0 : 0) + (b1<0 ? a0 : 0) off cell 2. The result is cells 0..2
shifted right 16 with Q.TO-INT twice. Clobbers A.

Checked at 32- and 64-bit cells, optimised and ASan+UBSan, on every pair
of 17 edge Q values plus 20000 pseudo-random pairs:
- against an independent reference (16-bit limbs, magnitudes multiplied
  schoolbook, negated, shifted), and
- against v3's q48_mul for non-negative operands.

The first draft applied only a1's sign correction; the reference caught
the missing b1 term (9860 failures at 32-bit cells).

v3 bug found, not fixed: q48_mul's portable branch (used where there is
no __int128) returns (result_hi << 16) | (result_lo >> 16); the high
part must move up 48, so it is wrong whenever the product passes bit
64, e.g. 0.5 * Q max gives 0000ffffffffffff instead of 3fffffffffffffff.
The parity reference here is that branch with the shift corrected,
which matches v3's __int128 branch.

Stack use is tight: Q.* leaves its caller 2 data cells and 1 return
entry, because UM* parks three values on the return stack. Recorded in
5.26; to be improved next.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 23:27:57 -04:00
rajamesandClaude Opus 5.5 d0accdbb88 feat(v4.0.0): D< and Q comparisons; call-free (D<), DMAX, DMIN, 2SWAP
Executed on the golden model as written, and correct: <, =, D<, 2OVER,
and Q.=, Q.<, Q.0= (the D=, D<, D0= words).

Fixed, because they could not work under D-2 or were wrong:
- DMAX, DMIN: 2OVER 2OVER D< needs 8 data cells plus D<'s 2, the whole
  10-deep data stack, so both failed on every case once the caller held
  anything. Rewritten on a new call-free helper
    (D<) ( d1 d2 -- d1 d2 flag )
  which compares copies of the high cells, then the low cells unsigned
  on a tie, with in-line sign tests; DMAX and DMIN then drop the loser.
- 2SWAP: ROT and SWAP written in line. Return headroom 2 -> 4, which
  also lifts 2OVER 1 -> 3 and Q.> 1 -> 2.
- Q.>: section 5.26 gave SWAP D<, which swaps single cells; it was wrong
  in 11702 of 20169 cases. Now 2SWAP D<.

Checked at 32- and 64-bit cells, optimised and ASan+UBSan: every
edge-vector quadruple (50625) for D<, (D<), 2SWAP, 2OVER, DMAX and DMIN;
Q comparisons on every pair of 13 edge Q values plus 20000 pseudo-random
pairs weighted to ties and one-bit differences, signed (D-8) and against
v3's unsigned comparisons where both values have the same sign.
Mutating any (D<) branch or subtraction fails 700+ checks.

The new fatal-UBSan setting caught a test-side array overflow in the
headroom probe (results buffer sized 4, six needed); fixed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 23:06:25 -04:00
rajamesandClaude Opus 5.5 167fdc3973 build(v4): make UBSan reports fatal in make sanitize
Without -fno-sanitize-recover=all, UBSan prints a report and the run
carries on to "all v4 tests passed". The sanitize build now aborts on
the first report. Verified by rebuilding the test-side shift bug found
in 981f4180 with these flags: the run exits 1 at the report.

Both test binaries now also depend on v4/Makefile, so a flag change
rebuilds them instead of leaving stale binaries in place.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 22:59:50 -04:00
rajamesandClaude Opus 5.5 981f4180ce feat(v4.0.0): Q.FROM-INT and Q.TO-INT as +* double shifts
DECOMPOSITION.md 5.26 described both words in prose only. They are now
written out, call-free, on one observation: +* with S = 0 never adds, so
each step is an exact arithmetic right shift of the double T:A.

  : Q.FROM-INT ( n -- q )  push 0 a! 0 pop  15 FOR +* UNEXT  push drop a pop ;
      T:A = n:0 is n * 2^N; N-16 shifts leave n * 2^16 (count 15 at
      32-bit cells, 47 at 64).
  : Q.TO-INT ( q -- n )    push a! 0 pop  15 FOR +* UNEXT  drop drop a ;
      16 shifts at every width; the low cell is left in A. Rounds toward
      minus infinity, as v3's arithmetic shift does.

Both clobber A; added to the section 2 list.

Checked at 32- and 64-bit cells, optimised and ASan+UBSan:
- Q.FROM-INT against n * 2^16 for edge values and 20000 pseudo-random n,
  and against v3's q48_from_u64 for n >= 0 (low cell at 64-bit, D-10).
- Q.TO-INT against v3's (int64_t)q >> 16 on the edge Q values and 20000
  pseudo-random Q values, and against a C double shift on 20000
  arbitrary doubles.
- Round trip Q.TO-INT(Q.FROM-INT(n)) = n.
Mutating either shift count or the S = 0 setup fails 40000+ checks.

Note: make sanitize prints UBSan reports but does not fail on them. One
was found here, in the test's own random shift amount (fixed); a grep of
the full sanitize output now shows no runtime errors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 22:58:04 -04:00
rajamesandClaude Opus 5.5 d948e0c1a3 test(v4.0.0): D-10 two-cell Q48.16; execute Q.ABS and Q.NEG against v3
D-10 (ruled 2026-10-02): a Q48.16 value is two cells at every cell
width, so every Q word is the same double word on 32- and 64-bit nodes.
Recorded in DECOMPOSITION.md section 3 and 5.26; the open note on the
Q.+/Q.- row is resolved by it.

Q.ABS and Q.NEG are the DABS and DNEGATE words. Checked against v3's
q48_abs and 0 - q on the 15 edge Q values and 20000 pseudo-random
values, at 32- and 64-bit cells, optimised and ASan+UBSan:
- 32-bit cells: bit-for-bit v3, including v3's wrap of Q min to itself.
- 64-bit cells: the low cell is v3's; the high cell is the true sign
  (so ABS and NEG of Q min give +2^63 rather than wrapping), per D-10.
Breaking DNEGATE's carry fails Q.NEG, Q.ABS and Q.- checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 22:54:40 -04:00
rajamesandClaude Opus 5.5 4f493fa317 test(v4.0.0): note Q.+/Q.- coverage in test_foundation.c header
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 22:52:54 -04:00
rajamesandClaude Opus 5.5 5ac52f19d6 test(v4.0.0): execute Q.+ and Q.- against v3 Q48.16
DECOMPOSITION.md 5.26 makes Q.+ and Q.- the D+ and D- words. They are
checked against v3's q48_add/q48_sub (uint64_t a + b, a - b, wrapping)
on every pair of 15 edge Q values (0, ulp, 0.5, 1.0, 1.5, -1.0, -ulp,
values across the 32-bit seam, Q max and min, +-12345.0) and 20000
pseudo-random pairs, 2682 of which overflow Q48.16.

- 32-bit cells: bit-for-bit v3's result, overflow wrap included.
- 64-bit cells, with a Q value as a sign-extended double: the low cell is
  v3's result; on overflow the high cell holds the true carry where v3
  wraps. Whether a Q value is one cell or two on a 64-bit node is not
  ruled; recorded as open in 5.26.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 22:52:27 -04:00
rajamesandClaude Opus 5.5 6a371b850e test(v4.0.0): execute M+, D-, D0= and D= on the golden model
All four run exactly as written in DECOMPOSITION.md 5.6/5.7 and need no
change now that D+ and DNEGATE are call-free.

Checked against C at 32- and 64-bit cells, optimised and ASan+UBSan:
every edge-vector combination (M+ over all triples, D- and D= over all
50625 quadruples, D0= over all pairs) plus 20000 pseudo-random cases
weighted to equal low or high cells and low sums that wrap to 0.
Mutations of D0= and M+ are caught.

Headroom (data cells under args / return entries under return address):
  M+ 6/4, D- 5/4, D0= 7/7, D= 5/3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 22:50:30 -04:00
rajamesandClaude Opus 5.5 092036c97a feat(v4.0.0): call-free D+ (D-2 stack headroom)
D+ as written in 5.7 was exact but kept two cells on the return stack
while calling U> -> SWAP/U<, leaving its caller one return entry: any
word calling a word that calls D+ (M+, D-, D=, Q.+, Q.-) would have a
return address silently overwritten.

The new D+ makes no calls. Both high cells wait on the return stack; the
carry out of the low-cell add comes from sign tests -- if the low cells'
top bits differ, there is a carry exactly when the sum's top bit is
clear; if they match, exactly when both are set.

Headroom (data cells under args / return entries): 4/1 -> 6/5.
Checked against C over all 50625 edge-vector quadruples and 20000
pseudo-random pairs (weighted to top-bit cases and low sums wrapping to
0), at 32- and 64-bit cells, optimised and ASan+UBSan. Retargeting each
of the four carry branches fails more than 13000 checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 22:46:11 -04:00
rajamesandClaude Opus 5.5 93d09840da feat(v4.0.0): SM/REM and DNEGATE call-free; execute /MOD and its section 5 words
SM/REM as written in section 4 never returned correctly for a negative
dividend. It holds three entries on the return stack and then calls
DABS -> DNEGATE -> D+ -> U> -> SWAP/U<, which overflows the 9-deep
circular return stack (D-2). DABS itself could not run: DNEGATE as
written (inv SWAP inv SWAP 1 0 D+) left its caller no return entries.

- DNEGATE: inv over if L1 drop push inv 1 + pop ; L1: drop 1 + ;
  i.e. ~d + 1, carrying into the high cell exactly when lo = 0.
- SM/REM: sign tests are native -if (as in 0<) and NEGATE is in line;
  the only calls are DNEGATE and UM/MOD, both call-free inside.

Executed on the golden model at 32- and 64-bit cells, optimised and
under ASan+UBSan:
- SM/REM on dividends built as q*n + r with |r| < |n| and r signed as
  d: every edge-vector q, n with r = 0 and r = +-(|n|-1), plus 20000
  pseudo-random cases.
- /MOD, U>, ABS, S>D, D+ and DABS as written in section 5, against C
  (D+ over all 50625 edge-vector quadruples).
Mutations of DNEGATE's carry and of each SM/REM sign branch are caught.

Headroom (data cells under args / return entries under return address):
  SM/REM 5/3, /MOD 5/2, DNEGATE 7/7, DABS 7/6, ABS 8/7, U> 6/5.
D+ as written is exact but leaves only 1 return entry; recorded in
DECOMPOSITION.md 5.7 as not yet revised.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 22:44:04 -04:00
rajamesandClaude Opus 5.5 3b9d1e4337 feat(v4.0.0): call-free UM/MOD loop for D-2 stack headroom
The first UM/MOD was exact but called 0<, U<, SWAP and OR inside its
loop, so it left its caller only 3 return-stack entries. SM/REM pushes
two signs before calling it, so under /MOD, M/MOD or */MOD the caller's
return address would be silently overwritten (D-2 circular stacks).

The loop now makes no calls. It branches on hi's top bit with -if, does
the unsigned hi' >= d test as U< does but with in-line sign tests,
subtracts with `inv a + inv`, and sets the quotient bit with `1 +` on an
even lo'. The final SWAP is in line.

Measured on the golden model at 32- and 64-bit cells:
  headroom   data 3 -> 6 cells under args, return 3 -> 6 entries
  speed      ~1355-1605 -> ~227-313 instruction words per call
Still exact for every uhi < ud (edge-vector triples and 20000 random
cases, optimised and ASan+UBSan). Retargeting each of the four in-loop
branches to the wrong label fails more than 12000 checks each.

DECOMPOSITION.md: section 4 UM/MOD replaced, with its derivation and
stack limits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 22:35:49 -04:00
rajamesandClaude Opus 5.5 41f59afeb3 test(v4.0.0): execute UM/MOD on the golden model; measure stack headroom
UM/MOD is assembled exactly as DECOMPOSITION.md section 4 gives it and
needs no change: it is exact for every uhi < ud at 32- and 64-bit cells.
It is checked against q*d + r = uhi:ulo with r < d through v4_umul, so
no second C divider has to be trusted. Coverage: all edge-vector triples
with uhi < ud plus 20000 pseudo-random cases (top-bit, small and
near-maximum divisors), optimised and under ASan+UBSan. Two hand
mutations each fail more than 15000 checks.

New headroom probe: runs a word with marked cells under the canary and
under its return address and reports how many survive, since the D-2
circular stacks overwrite silently instead of faulting. Measured:
  UM*     6 data cells under its args, 4 return entries under its return
  UM/MOD  3 data cells under its args, 3 return entries under its return

DECOMPOSITION.md: UM/MOD marked executed, with its defined range and
stack limits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 19:48:04 -04:00
rajamesandClaude Opus 5.5 9a391eb0ad feat(v4.0.0): full-range UM* under plain F18 +* (D-3)
UM* as first written in DECOMPOSITION.md section 4 was exact only while
u1 <= 2^(n-2): plain +* loses the carry out of T and its shift keeps T's
sign bit, so the loop is exact only while S and T stay in
[-2^(n-2), 2^(n-2)).

The rewrite multiplies by s = u1 2/, which always lies in that range,
starting T at t0 = u2 2/ when u1 is odd, so the loop yields
hi:lo = t0 + s*u2 exactly. Then
  u1*u2 = 2*(hi:lo) + c_lo + c_hi*2^n
  c_lo  = u1 & u2 & 1
  c_hi  = (u1<0 ? u2 : 0) + (u1 odd and u2<0 ? 1 : 0)
restores the halved-away bits and the unsigned reading of both top bits.

test_foundation.c runs the new definition against v4_umul over every
pair of the edge vectors plus 20000 pseudo-random pairs, at 32- and
64-bit cells, optimised and under ASan+UBSan. The two pinned failing
cases are now ordinary exactness checks. Two hand mutations of the
correction step each fail more than 10000 checks at both widths.

DECOMPOSITION.md: section 4 UM* replaced, D-3 ruling text updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 19:21:17 -04:00
rajamesandClaude Opus 5.5 067f317c47 feat(v4.0.0): hosted golden model, single node
First code for StarForth v4 (JUSTIFICATION.md section 10, step 1): one node
of the 32-instruction core as a C99 model, with cell width as a build
parameter.

- Node: P, A, B, F18 circular stacks (10 and 9 deep, D-2), word-addressed
  memory (D-1), 5% guard bands on every bounded list.
- Instruction word: six 5-bit slots in 32 bits at every cell width.
- Executor: all 32 opcodes of DECOMPOSITION.md 1.3. Cell arithmetic wraps
  explicitly; no signed overflow or implementation-defined shift.
- Heat: per-opcode and per-call-target counters and the anti-clock, driven
  by instruction retirement (1.4, D-6 interim).
- Slot packer and runner for tests, and a reference unsigned multiply in
  plain C99 with no 128-bit type.

Tests run at 32- and 64-bit cells, and under ASan and UBSan. They cover
every opcode and execute the first section 4 definitions (NIP SWAP OR
NEGATE ROT 0< 0= 2DUP - U<) against the C operation each stands for.

UM* as written in section 4 is exact only while u1 <= 2^(n-2). Two known
failing cases are pinned in test_foundation.c until it is rewritten.

DECOMPOSITION.md: record D-9, the instruction word is 32 bits at every
cell width (ruled 2026-10-02).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 14:38:27 -04:00
rajames 1f98034311 Merge remote-tracking branch 'origin/StarForth-v4.0.0' into StarForth-v4.0.0 2026-10-01 15:55:37 -04:00
rajamesandJunie a8b70e88d3 Reorganize source tree: kernel/, v3/, v4/ split and board infrastructure
Source tree reorganization:
- Move StarForth v3 engine to v3/ (src/, include/, Makefile)
- Move kernel to kernel/ (src/, include/, linker/, Makefile)
- Create v4/ skeleton for F18-ISA golden model (DECOMPOSITION.md, JUSTIFICATION.md)
- Move FABRIC-0..4.md to docs/fabric/
- Move ONTOLOGY.md and ROADMAP.md to docs/

Board infrastructure:
- Add boards/ser5/, boards/raspi/, boards/milkv/, boards/zynq7020/
- Each board has board.mk (ISA, CPU flags, boot recipe) and README.md
- Root Makefile becomes thin dispatcher: boot_image, all, clean, docs take TARGET
- make boot_image TARGET=SER5|RASPI|MILKV builds one GPT/MBR image per board
- ZYNQ7020 target exists but stops with clear error (ARMv7 port not built yet)
- scripts/mkdiskimage.sh builds disk images for all boards

Docs pipeline:
- docs/book/ with LaTeX master (main.tex) and Makefile
- pandoc converts Markdown to LaTeX at build time
- Two Lua filters: table-widths.lua (wide tables wrap), code-breaks.lua (inline code breaks)
- make docs builds single PDF (754 pages, 0 missing characters)
- make docs TARGET=<board> adds board appendix
- build/docs/<book|board>/meta.tex stamps git commit into PDF

Bug fixes:
- 42 include paths that only worked by accident now use correct relative paths
- clang-18 hardcode replaced with configurable CC variable (fixed aarch64 build)
- Pi 5: kernel_2712.img linked at 0x80000, .bss zeroed, memory reserved
- Doxyfile, .clang-tidy, README.md, Kconfig paths updated

Verified:
- Hosted v3 build passes 1012 tests, 0 failures
- SER5 image boots in QEMU (OVMF), POST passes, K exact (65536 = Q48_ONE)
- Milk-V image boots in QEMU (OpenSBI + U-Boot + bootefi), POST passes
- make clean TARGET=<board> removes only that board and its ISA objects
- make all builds all boards, hosted v3, and docs in one run

Co-authored-by: Junie <junie@jetbrains.com>
2026-10-01 15:40:09 -04:00
Claude f238f02afa docs(v4.0.0): record D-1..D-8 rulings in DECOMPOSITION.md
D-1 word addressing; D-2 F18 circular stacks (10/9 deep), hidden, so
DEPTH/PICK/ROLL/.S/SP@/SP! are retired everywhere and the DSP register is
dropped; D-3 plain F18 +* (UM* flagged for revision); D-5 host width
matches the host CPU; D-7 moot; D-8 signed Q48.16; D-4 and D-6 deferred
to the hosted-mesh step. Adds a note that every CAP definition must be
re-checked against the 10/9 stack depths.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BY9HMwK5Cetz3caBgHGyds
2026-10-01 00:55:20 +00:00
Claude 3c709c115b docs(v4.0.0): add StarForth v4 justification and primitive decomposition
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
JUSTIFICATION.md records why v4 exists and the reasoning behind each
major design decision. DECOMPOSITION.md assigns every v3 C primitive a
fate on the 32-instruction F18-derived core.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BY9HMwK5Cetz3caBgHGyds
2026-09-29 06:50:28 +00:00
admin 357cb5b4ac Merge pull request 'docs: add StarForth primitive word reference' (#2) from claude/wizardly-tesla-36lqlt into master
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
Reviewed-on: https://gitea.strshipos.org/admin/LithosAnanake/pulls/2
2026-09-29 05:22:50 +00:00
Claude 2fcc468ecb docs: add StarForth primitive word reference
Lists every C-registered StarForth primitive with stack notation and
usage notes, grouped by module, plus a section on implementation quirks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BY9HMwK5Cetz3caBgHGyds
2026-09-29 05:19:09 +00:00
Robert Allan JamesandClaude Sonnet 5 6302dcb50e FABRIC-3.7.md: record Phase 8 v3 closure (in-system block-copy defense)
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
Distinguishes it clearly from the still-accepted "cloned outside
StarshipOS entirely" limitation this document already settled -- Phase
8 v3 closes a narrower, different threat: cloning block content using
StarshipOS's own console primitives, now refused by MOVE/CMOVE/CMOVE>/
RELOCATE-BLOCK for cross-device copies.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 04:45:06 -04:00
Robert Allan JamesandClaude Sonnet 5 26c1117ccd Phase 8 v3: refuse a same-VM, cross-device raw block copy from within StarshipOS
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
Two research passes confirmed the identity record itself (seed/pubkey/
cert) is already unreachable from any FORTH primitive -- only C-level
read_devblock/write_devblock touch it. But a device's ordinary user block
content CAN be copied between two attached devices today, using only
stock, unpinned words: <src> BLOCK <dst> BUFFER 1024 MOVE UPDATE
SAVE-BUFFERS, or the dedicated RELOCATE-BLOCK word (whose own doc comment
already admits "performs no policy validation of its own"). Checked
whether the existing per-block owner_fp/BLK-ACL-ALLOW@ metadata already
solves this -- it doesn't: owner_fp encodes who (a VM identity pubkey),
never where (physical device), and blk_get_buffer()/blk_update() never
consult acl_allow/acl_ttl at all -- those fields are completely inert.

Small, targeted fix, no rearchitecture:

- blk_subsys_relocate_block() (block_subsystem.c): same-device check.
  Its own documented purpose is wear-leveling (relocate on the SAME
  device) -- never stated as cross-device, and nothing enforced that
  until now.
- New public blk_lbn_device_handle() (block_subsystem.c/.h): the missing
  LBN-to-device direction (blk_get_device_range() already goes the other
  way). Opaque, stable, == comparable.
- MOVE (memory_words.c) and CMOVE/CMOVE> (string_words.c): refuse when
  both addresses are block-window addresses backed by two different
  devices -- the exact shape of the composed attack. A copy where either
  end is ordinary VM memory (the overwhelming common case: staging text
  from PAD, editing a block in place) is untouched.
- blk_vm_check_epoch()/blk_vm_slot_for_addr() exposed (block_words.h) so
  the two new call sites share the same window-slot invalidation contract
  rather than a second, divergent copy of it.

Verified live on all three architectures, not just boot-clean: same-
device MOVE/RELOCATE-BLOCK still succeed exactly as before; cross-device
MOVE/CMOVE/RELOCATE-BLOCK all refused. Zero UNKNOWN WORD, identical
dict_hash across all three (this change adds no FORTH-visible word, only
internal refusal conditions, as predicted).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 04:44:22 -04:00
Robert Allan JamesandClaude Sonnet 5 b7d9ed5425 FABRIC-3.7.md: settle drive-cloning defense as rejected, not undesigned
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
Captain Bob rejected a PIN/passphrase second factor firmly and directly
after it was built and live-tested on all three architectures: "nothing
like a pin or a password or secret code or any bullshit... Everybody has
secrets. There's only the drive." All PIN-related code (KDF, XOR
keystream seed encryption, no-echo input, MINT/WIREBIND prompts,
user_identity_seed_t v3 format) was reverted before commit -- none of it
ever landed in git history.

This project's identity model has no knowledge factor, by design:
physical possession of the thumbdrive is the entire credential. A
byte-for-byte clone being equivalent to the real drive is the accepted
model, not a gap needing a fix. Recorded here so future work doesn't
default back to a PIN/password approach.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 03:42:38 -04:00
Robert Allan JamesandClaude Sonnet 5 81049da268 Correct FABRIC-3.7.md: the original elevation-entrypoint bug diagnosis was wrong
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
Found while starting Part A's implementation, before any code was written
against the original design: reading the actual deleted SEND-ELEVATE-REQUEST
source (git show 3e201c8^:capsules/common/messaging.4th, block 5040) shows
it copied the target word's name as literal character bytes into a scratch
buffer, building "S" <name-text>" <pk0> <pk1> <pk2> <pk3> ELEVATE-GRANT"
entirely in the sending VM's own memory, then sent that finished string --
never a raw address -- to Hera. waddr/wu never crossed the VM boundary as
numbers anywhere in this flow. The original write-up reasoned from
ELEVATE-GRANT's own signature alone, without first reading how the caller
actually built its message.

Corrected in place, wrong original text kept struck-through for
traceability rather than deleted, per this series' own convention.

Net effect: Part A (the buffer/message redesign) is not needed -- the
original mechanism was already safe. Part B (capsules/zuse.4th, already
committed and three-arch verified this session, 089ab21) stands on its
own, unaffected.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 23:03:47 -04:00
Robert Allan JamesandClaude Sonnet 5 089ab2160e Phase 8 Part B: gate ZUSE-ELIGIBILITY-ADD, closing the no-drive-needed privilege path
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
ZUSE-ELIGIBILITY-ADD's own doc comment admitted "no authorization check
here or anywhere else... applied later if and when actually needed --
not invented here." That's now: anyone reaching a Hera FORTH prompt
could add their own pubkey to the eligibility list with zero legitimate
identity material -- no minted drive, no WIREBIND, no cert-signature
check involved at all. Once a future caller reaches ELEVATE-GRANT again,
a self-added pubkey would pass zuse_eligibility_is_member() and grant
ACL-ALLOW!/ACL-TTL! on any named word.

Fixed the FORTH-only way, matching this project's own convention (ACL
policy belongs in ACL.4th, never in C; never gate on zuse_session in C --
her power is the absence of ACLs, not a hardcoded session check):
ZUSE-ELIGIBILITY-ADD is now denied by default (capsules/zuse.4th block
4016), granted and pinned only inside ACL-ZUSE-BOOT's already-existing
authenticated branch (block 4017) -- the same gate her own god-mode
already goes through, requiring a real cert-verified Zuse before it opens.

Live-verified on all three architectures, not just boot-clean: after
genesis authentication, ACL-ALLOW@ and ACL-PINNED? both read -1, and
HERE ZUSE-ELIGIBILITY-ADD executes successfully past the ACL gate.

Phase 8 v1 plan: /home/rajames/.claude/plans/jiggly-cuddling-stallman.md
Part A (the ELEVATE-GRANT pointer-confusion fix, FABRIC-3.7.md) is
separate, not yet built.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 22:57:10 -04:00
Robert Allan JamesandClaude Sonnet 5 2406158668 Fix include/version.h collision between hosted Makefile and Makefile.starkernel
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
The hosted Makefile writes include/version.h with FORCE as a prerequisite
(always regenerates), but Makefile.starkernel's own rule had no
prerequisite at all -- Make only rebuilds a target with no prerequisites
when the file is missing. Since both Makefiles write the same path with
incompatible content (the hosted version has no LITHOS_VERSION/
LITHOS_VERSION_STR at all), running a bare `make -f Makefile.starkernel`
after a hosted `make` build silently reused the wrong file and failed
deep in kernel_main.c with "LITHOS_VERSION_STR undeclared".

Added FORCE (declared .PHONY, matching the hosted Makefile's own existing
pattern) as include/version.h's prerequisite in Makefile.starkernel.
Verified the fix directly: poisoned version.h with a hosted build, then
ran a bare (non-clean) kernel build and confirmed it self-heals.

Full three-architecture acceptance: amd64 (logs/20260922-215333/),
aarch64 (logs/20260922-215803/), riscv64 (logs/20260922-220217/) -- all
three reach [zuse@Hera] ok>, zero UNKNOWN WORD.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 22:34:54 -04:00
Robert Allan JamesandClaude Sonnet 5 2008c4596a Add FABRIC-3.7.md: Phase 8 PKI elevation-entrypoint design, fixes a real pointer-confusion hole
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
New document, not a reopening of the closed FABRIC-3.5.md/FABRIC-3.6.md --
successor for exactly one topic, per those documents' own close discipline.

Records a security defect found by inspection while auditing Phase 4's
collateral damage to ELEVATE-GRANT: the old SEND-ELEVATE-REQUEST mechanism
passed a raw address (waddr/wu) computed in the sending VM's own memory
space across to Hera, which dereferences it in Hera's own space --
per-VM vaddr_t means those are never the same address space. Whoever
controls waddr controls what dictionary entry NAME>XT resolves to on
Hera, independent of the caller's actual pubkey/eligibility.

Design fix: never cross an address, only ever cross bytes -- generalizes
this session's own payload-aliasing fix (SkHermesMessage.payload_buf) one
level up. Send the target word's name as inline payload bytes, copy them
into a fixed kernel-owned buffer already in Hera's own memory on receipt,
and hand vm_interpret() only kernel-controlled integer literals referencing
that buffer. ELEVATE-GRANT itself is unchanged -- policy logic stays in
FORTH, per ACL.4th's own rule.

No code written or authorized by this document.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 21:44:06 -04:00
Robert Allan JamesandClaude Sonnet 5 7306872848 Phase 5.7: archival close of FABRIC-3.5.md and FABRIC-3.6.md at v2.1.0
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
FABRIC-3.5.md's provisional "design phase closed, not yet archival" header
replaced with the real CLOSED/ARCHIVAL form per its own §XXVI.5 spec,
naming v2.1.0 -- prior status headers kept underneath, not deleted.

FABRIC-3.6.md gets the same treatment: a CLOSED/ARCHIVAL banner above the
START HERE section, which stays as historical record rather than being
removed. Neither closure triggers the FABRIC-0 -> -1 -> -2 -> -3
carry-forward chain (both are standalone topic documents) and neither
touches FABRIC-3.md, which remains open for its own topic.

The Tripod/kernel reshuffle is complete: Hermes moved into the kernel as
kernel-Hermes, the Tripod is Hera/Artemis/Hestia, tagged v2.1.0.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 19:53:57 -04:00
Robert Allan JamesandClaude Sonnet 5 8edbd3cce6 Phase 5.5: investigate stray v2.0.1 branch and PR #1, don't delete/close (FABRIC-3.6.md)
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
refs/heads/v2.0.1's tip is the exact merge-base with master -- a strict
ancestor, fully subsumed, confirmed by its own commit message ("master
fast-forwarded to v2.0.1, verified"). Stale ref hygiene debt, safe to
delete, not deleted here -- needs Captain Bob's explicit go-ahead.

PR #1 turned out not to be a stray reference at all: it's a PR from this
same branch against master, already closed (not merged) 20 seconds after
it was opened on 2026-09-18, before any of this reshuffle's work existed.
Nothing to resolve beyond recording what it is.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 19:50:03 -04:00
Robert Allan JamesandClaude Sonnet 5 2a084ba037 Phase 5 corrections: sharpen 5.1's boundary claim, fix hosted Makefile's own stale VERSION
Advisor review of the Phase 5 close-out commit caught two real issues:

- Task 5.1's write-up claimed the Isabelle pass "confirms no regression" --
  overstated. Nothing in proof/'s scope changed, so the pass isn't
  regression evidence, it's a build-completeness formality; the boundary
  argument alone already supports the real conclusion. Sharpened.

- sbom.spdx was regenerated before the 5.4 version bump, so it briefly
  understated the engine version. Root cause: the hosted Makefile carries
  its own separate hardcoded VERSION (Makefile:17, still 3.1.0), distinct
  from Makefile.starkernel's copy that 5.4 bumped -- duplication CLAUDE.md's
  own "two independently tracked version strings" note doesn't document.
  Bumped to 3.2.0 to match, regenerated (PackageVersion now correct), and
  rebuilt the hosted starforth binary so lfs/amd64/starforth reflects it.

Also recorded why 5.1-5.4 landed as one commit (deviation from this
document's per-task-commit discipline) and sharpened the riscv64 log
entry so the FAILED run and the accepted retry aren't ambiguous to a
future reader grepping logs/.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 19:49:08 -04:00
Robert Allan JamesandClaude Sonnet 5 a4ad14aec6 Phase 5 close-out: Isabelle pass, doc sweep, SBOM, version bump (FABRIC-3.6.md tasks 5.1-5.4)
5.1: Isabelle/HOL pass (52 theories, clean) -- restated the boundary rather
than just citing the green build: proof/ scope was already entirely
outside this reshuffle's footprint (src/starkernel/, capsules/*.4th),
so the boundary is unchanged, not moved.

5.2: Documentation sweep. CLAUDE.md's stale WIP banner and Tripod fleet
description updated now that Phases 0-4 have actually landed (Hera/
Artemis/Hestia, no Hermes). MANIFEST.md rides the strip -- hermes/init.4th's
block table replaced with a deletion note, init.4th/doe-campaign.4th/
hestia/init.4th entries corrected to match the post-strip live files.
Confirmed the TRIPOD.md/0.1 contradiction was already resolved (2026-08-13).
Settled the superseded-docs call explicitly: archive as-is, do not rewrite.
Fixed experiments/bare_metal/README.md's block-size framing (still said
1024-byte budget; real rule is 64 chars x 16 lines). K-qualification
checked clean against the two living documents; full retroactive sweep
of the closed archival FABRIC corpus explicitly declined as disproportionate.

5.3: make sbom. Installed syft (user-local, approved). Found and fixed a
real Makefile bug while at it -- the sbom target hardcoded
--source-name StarForth, so DocumentName was wrong even after regenerating.

5.4: LITHOS_VERSION 2.0.0 -> 2.1.0, engine VERSION 3.1.0 -> 3.2.0 (minor,
per the dictionary-visible-only rule). Replaced the stale version-comment
block in Makefile.starkernel (had the odd/even LTS rule backwards) and
docs/lithosananke/ROADMAP.md's retired versioning-policy section with the
ratified ladder. Verified on all three architectures; riscv64's first pass
hit a transient virtio_blk timeout during boot-time Zuse genesis mint,
reported and confirmed non-reproducing on an immediate clean retry.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 19:46:34 -04:00
Robert Allan JamesandClaude Sonnet 5 3e201c82a5 Stage E: Category B strip -- remove Hermes, messaging.4th, old routing (FABRIC-3.6.md task 4.1-4.4)
All FORTH-owned message types were cut over to kernel-Hermes in Phase 3
(tasks 3.8-3.10). This removes the now-dead FORTH messaging layer and
the Hermes VM itself: capsules/common/messaging.4th, capsules/hermes/init.4th,
the slot-3 VM-NAME-REG pairing convention, and every load-site/birth-site
reference across capsules/init.4th, artemis/init.4th, hestia/init.4th,
doe-campaign.4th (Artemis-only now), capsule_console.c, capsule_mint.c,
capsule_wirebind.c, capsule_birth.c, and kernel_main.c.

Verified on all three architectures: clean boot, mkcapsule --lint clean
(36 files, 0 violations), zero UNKNOWN WORD, identical dict_hash across
amd64/aarch64/riscv64 for every VM, and a full mint -> WIREBIND-attach ->
USE -> relay round-trip exercising the two highest-risk edits
(capsule_console.c/capsule_mint.c).

Found, not fixed: deleting messaging.4th removes SEND-ELEVATE-REQUEST,
which was the only caller of KH-ELEVATE-SEND and the only path to
ELEVATE-GRANT (zuse-eligibility.4th, still loaded at boot) -- Phase 8
PKI's own elevation entrypoint. Needs a decision before Phase 5 close-out.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 18:29:51 -04:00
Robert Allan JamesandClaude Sonnet 5 cab9b5a31f Stage D: ELEVATE-REQUEST real cutover -- FABRIC-3.6.md task 3.10
Reachability verified live before writing any code, per this
project's own standing rule (grep cannot establish reachability
alone): FIND SEND-ELEVATE-REQUEST / FIND ELEVATE-GRANT / FIND
CH-REQUEST all resolve on a live Hera boot, though grep across
capsules/experiments/docs found zero callers of SEND-ELEVATE-REQUEST
-- a real, complete, directly-callable entrypoint (H.5/H.8's own
design) with no current automatic trigger, not dead code.

Correction to a prior finding, made in the course of this check: task
3.8's write-up claimed "Hera's own pre-existing inability to load
common:messaging.4th" -- false. capsules/init.4th (Hera's own
MAMA_INIT capsule) loads it directly, and SEND-ELEVATE-REQUEST lives
and works in her dictionary right now. Task 3.8's own actual scope is
unaffected by this correction.

Cutover: SEND-ELEVATE-REQUEST (messaging.4th) no longer ends in
CH-REQUEST's COMMON-CH/MSG-SEND path; it now calls KH-ELEVATE-SEND
(repl.c), a new C word wrapping sk_hermes_send_one(), registered
unconditionally for every VM. from/to are derived from the calling VM
and sk_get_mama_vm() directly in C, never taken from the stack -- a
real correctness improvement over CH-REQUEST's own initiator-only
gate, which only existed because a caller COULD pass the wrong from
value; deriving it in C makes that spoof structurally impossible.
SK_HERMES_MSG_TYPE_ELEVATE_REQUEST reuses ELEVATE-REQUEST's own value
(8), same partition-rule reasoning as tasks 3.8/3.9. Delivery is
unchanged task 3.4 machinery. No new static-buffer lifetime caveat --
the payload-aliasing fix landed before this task started.

CH-REQUEST (messaging.4th) is now dead code, its one real caller just
removed -- found, not fixed, per Captain Bob's Law.

Verified live on all three architectures: 0 0 0 0 S" DUP"
SEND-ELEVATE-REQUEST (deliberately-invalid pubkey, so ELEVATE-GRANT
correctly refuses -- the check is the pipeline running, not a grant
succeeding) fires the evidence line and completes cleanly, DUP
unaffected afterward. Zero UNKNOWN WORD, dict_hash identical across
all three architectures (changed uniformly from prior runs -- one new
word registered -- not diverged, matching SXXXIV.6's own rule).

This closes task 3.11 (Phase 3 gate): all of messaging.4th's live
FORTH-owned message types (BLK-ATTACH-EVENT, CONSOLE-CMD-EVENT,
ELEVATE-REQUEST) are now real kernel-Hermes cutovers. Phase 4 may
begin.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 17:00:11 -04:00
Robert Allan JamesandClaude Sonnet 5 d4f8a568ee Silence routine diagnostic noise; fix a real CRLF double-submit bug
Two separate fixes, found and closed together after task 3.9/prompt-
format landed (Captain Bob: "finish the work first then we'll do
cleanup before 3.10 begins").

1. Logging noise (confirmed live via QMP screendump): three sources
   were cluttering ordinary interactive console sessions.
   - INFERENCE "Output validation failed, ignoring results"
     (vm_runtime.c, kernel; vm_time.c, hosted mirror) and xhci "CSW
     status = FAILED"/"unit not ready -- retrying" (xhci.c) were
     already log_message(LOG_WARN/LOG_ERROR, ...) calls, just visible
     at the default runtime LOG_WARN level -- downgraded to LOG_INFO,
     all three fire routinely and self-resolve (xhci.c's own existing
     comment already documents the retry as expected SCSI UNIT
     ATTENTION behavior, not a driver defect).
   - Stadium: dispatch cell=... (stadium.c's stadium_dispatch()) was a
     genuine defect: an unconditional console_puts()/console_println()
     sequence with no level gating at all, printing on every single
     dispatch. Rewritten through log_message(LOG_DEBUG, ...).

2. CRLF double-submit (found while investigating why the cleaned-up
   noise still didn't look like a normal single-VM session):
   sk_console_readline() (repl.c) breaks on '\r' OR '\n' as independent
   terminators, so a line sent as both bytes submits twice -- the real
   line, then an immediate empty-line submit on the second byte, each
   printing its own " ok". Pre-dates Stage D entirely, not an async-
   relay artifact. Fixed with a single non-blocking peek-and-discard
   for the paired byte right where the line terminates.

Verified live on all three architectures for both fixes: zero UNKNOWN
WORD, dict_hash identical to every prior acceptance run in this
document (both changes are display/interaction-only).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 16:33:29 -04:00
Robert Allan JamesandClaude Sonnet 5 5c07745c18 Fix SkHermesMessage payload-aliasing defect (task 3.8 findings log)
sk_hermes_send_one() -- the single funnel every sender, including
sk_hermes_publish(), already goes through -- used to store the
caller's own payload_addr pointer as-is. Two sends before either
drains meant both messages pointed at the same caller-owned buffer,
whichever send wrote last silently winning: real, confirmed live (a
second identity thumbdrive attached at boot alongside Zuse's own left
its WIREBIND pairing silently never happening).

SkHermesMessage gains an inline payload_buf[SK_HERMES_CHUNK_MAX_
PAYLOAD] field; sk_hermes_send_one() now memcpy()s the caller's
payload into it and points payload_addr at that copy instead. No
sender or reader call site needed to change -- every existing reader
already only ever reads through payload_addr, which still points at
valid bytes of the same length, now message-owned. g_kh_blk_attach_buf/
g_kh_console_cmd_buf (repl.c, tasks 3.8/3.9) no longer need to survive
past their own send call; their doc comments, which had claimed the
old aliasing shape was benign, are corrected.

Verified the original bug is actually gone: reproduced the exact
original scenario (a real, sequentially-minted rajames identity
attached at boot alongside Zuse's own, two simultaneous BLK-ATTACH-
EVENT sends in one idle-loop pass) -- WIREBIND pairing, USE, and the
Stage D relay all now work where WIREBIND previously silently failed.
Standard three-ISA acceptance also clean: zero UNKNOWN WORD, dict_hash
identical across all three and matching every prior acceptance run in
this document.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 14:16:29 -04:00
Robert Allan JamesandClaude Sonnet 5 21734b1a52 Console prompt: identity/machine both sides once redirected off Hera
sk_console_user_prefix() (repl.c) previously always returned "zuse"
(or the WIREBIND-attached username) as the left side of the bracket
prefix, regardless of which VM the console was actually pointed at --
"[zuse@rajames]" after USE rajames, always showing the authenticating
superuser rather than the active identity.

Changed on Captain Bob's direct instruction: once the console is
redirected into a WIREBIND identity's own console VM
(console_get_vm_name() != "Hera"), show that same name on both sides
-- "[rajames@rajames]" -- since WIREBIND births the console VM
literally named after the identity, so the identity IS that VM, not a
separate label. At the top level (still on Hera, nothing has
redirected yet), the original zuse_session/WIREBIND-username logic is
unchanged.

An earlier, more ambitious attempt (separate identity/machine tracked
state across every console_set_vm_name() call site) regressed live to
a wrong [zuse@Artemis] prompt and was fully reverted before reaching
any acceptance run -- the landed fix needed none of that new state,
just this one function.

Verified live on all three architectures: [zuse@Hera] at the top
level and after a live WIREBIND attach (before USE), [rajames@rajames]
after USE rajames, with task 3.9's Stage D relay still firing
correctly on top of it. Zero UNKNOWN WORD, dict_hash unaffected
(display-only change).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 13:50:40 -04:00
Robert Allan JamesandClaude Sonnet 5 3975dc61cd Stage D: CONSOLE-CMD-EVENT real cutover -- FABRIC-3.6.md task 3.9
Send-side cutover: sk_repl_dispatch_line()'s FORTH-string
"CONSOLE-CMD-EVENT 0 3 S\" ...\" 0 MSG-SEND" interpret is replaced with
a direct sk_hermes_send_one() call (SK_HERMES_MSG_TYPE_CONSOLE_CMD,
kernel_hermes.h, deliberately reusing CONSOLE-CMD-EVENT's own value 7,
same partition-rule reasoning as task 3.8's BLK-ATTACH-EVENT cutover).

Real finding along the way: kernel-Hermes's drain only ever runs as a
side effect of vm_interpret() being called on the target VM. Task
3.8's target (Hera) is always being interpreted via the interactive
REPL loop; task 3.9's target is a WIREBIND identity's own ~user VM, a
passive receiver nothing else drives. The existing idle-loop pump only
ticked VMs with the old FORTH MSG-TICK word ACL-allowed -- a VM minted
with the STD79-lockdown personality never has it, so the pump silently
skipped it forever and queued messages never delivered. Fixed by
adding an unconditional, direct sk_hermes_drain_checkpoint() call in
the same pump loop, independent of the MSG-TICK gate.

Verified live on all three architectures: WIREBIND-attach a real
identity, USE into it, type a plain console line, confirm the Stage D
evidence line and correct relayed execution result. Zero UNKNOWN WORD,
dict_hash identical across all three ISAs and matching task 3.8's own
baseline. The FABRIC-3.md-documented USE/BINDSTEP crash did not
reproduce in any of these live sessions (recorded as a finding, not
chased further).

Depends on the zuse_root_pubkey_known fix already landed in ac4d431 --
without it, WIREBIND cannot attach any identity on a fresh boot at
all, which blocked this task's own verification until found and fixed
separately.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 13:34:20 -04:00
Robert Allan JamesandClaude Sonnet 5 ac4d431aee Fix zuse_root_pubkey_known never set after a same-session genesis mint
capsule_zuse_boot_load_root_pubkey() is the only writer of
zuse_root_pubkey_known, and it only ever runs once, synchronously, at
Artemis's boot-time virtio-blk attach -- before genesis mint has
happened on a fresh artemis.img, so it finds no marker yet and leaves
the flag 0. Nothing re-triggers it after genesis mint completes.

Silent result: capsule_wirebind_try_attach()'s own
`if (!mama_vm->zuse_root_pubkey_known) return;` gate then refuses
every identity attach for the rest of that boot, with no message at
all -- meaning WIREBIND was silently dead on any boot that reset
artemis.img fresh, which is exactly what this project's own
acceptance convention does before every run.

Found live verifying FABRIC-3.6.md task 3.9's console-session
acceptance. Fixed at the source: install_and_activate() already has
the root pubkey in hand (from genesis mint or an already-Zuse
re-attach) -- activate it directly there instead of relying on a disk
re-read that may never happen in-session.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 12:38:40 -04:00
Robert Allan JamesandClaude Sonnet 5 7944abca36 Findings: task 3.8 payload-aliasing defect found while starting 3.9
While orienting for task 3.9 (CONSOLE-CMD-EVENT cutover, per Captain
Bob's ruling to verify via a real QMP/serial-socket console session),
booting with a second real identity drive attached alongside Zuse's
own exposed a real defect in the already-closed task 3.8 code:
g_kh_blk_attach_buf (repl.c) is one static buffer, and
sk_hermes_send_one() stores payload_addr as a caller-owned pointer,
not a copy. Two real USB-MSC attaches in one sk_repl_idle() pass send
before either drains, so both messages alias the same buffer -- only
one identity ever completed.

Task 3.8's own write-up claimed this "carries the same single-buffer-
reuse shape ATTACH-ACK-BUF itself already had... not a new hazard" --
that claim was wrong and is amended in FABRIC-3.6.md's findings log.
FORTH's own MSG-SEND had the identical pointer-aliasing shape but
never hit the window: MSG-TICK drained from the same sk_repl_idle()
pass that queues attaches. Kernel-Hermes drains at interpret
checkpoints, which don't fire during that pass at all -- the cutover
changed not just how delivery happens but when, opening a window
FORTH's own design never had.

Reported, not fixed here, per Captain Bob's Law: this is a defect in
closed task 3.8 code, found while scoping a different task. A real
fix changes SkHermesMessage's own shape to own its payload bytes
rather than reference a caller's pointer -- bigger than a repl.c
patch, touches every existing sender, needs its own three-ISA
acceptance.

Task 3.9's own send-side cutover (kernel_hermes.h, repl.c) is written
but deliberately left uncommitted -- it would inherit the identical
defect shape if shipped now. logs/20260922-111840/amd64/ is the
reproduction.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 11:26:13 -04:00
Robert Allan JamesandClaude Sonnet 5 bbfd9103f4 Stage C: cut over BLK-ATTACH-EVENT alone -- FABRIC-3.6.md task 3.8
The reply leg (Artemis -> Hera ack) that used to flow through
common:messaging.4th's MSG-SEND/MSG-TICK now goes through
kernel-Hermes's sk_hermes_send_one()/sk_hermes_drain_checkpoint()
instead -- FORTH Hermes never sees a BLK-ATTACH-EVENT message again
(SXXXIV.2's partition rule). The request leg was never real FORTH
messaging traffic to begin with (a direct VM-EXEC, no type tag,
forced by Hera's own inability to load common:messaging.4th), so it
is untouched.

New KH-BLK-ATTACH-SEND (repl.c) wraps sk_hermes_send_one(), reached
from capsules/artemis/init.4th's HERA-BLK-ATTACH-REQ. Delivery reuses
task 3.4's already-wired sk_hermes_drain_checkpoint(); BLK-ATTACH-ACK
itself is unchanged, just reached by a different layer.
SK_HERMES_MSG_TYPE_BLK_ATTACH deliberately reuses BLK-ATTACH-EVENT's
own value (9) to document this as a cutover of the same message, not
a new one.

Two real bugs found on the way, both recorded in FABRIC-3.6.md's
findings log:
- A popped FORTH CREATE-buffer address was raw-cast to a host pointer
  instead of going through vm_ptr() -- silently read all-zero memory,
  no crash, no error, just a message that arrived and did nothing.
  Fixed; the rule and its exception (repl.c's own dev-addr is
  legitimately a raw pointer, formatted that way by its own pushing
  code) are written up for the next FORTH-facing C word.
- A separate, genuine hang on the very first live exercise of this
  path, never reproduced across ten subsequent boots. Reported, not
  chased -- not blocking, per the task's own check being otherwise
  fully satisfied.

Also found live: log_message() is invisible in this build's actual
serial-log capture at every level -- settled on a single
console_println in the real drain target instead, one line per real
USB attach, not a hot-path.

Final acceptance (logs/20260922-105501, -105758, -110304, disk images
reset before each): dict_hash identical across all three
architectures for every VM, zero UNKNOWN WORD, mkcapsule --lint
clean, real ledger+stadium_conserved(Artemis)=true evidence on every
boot.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 11:08:26 -04:00
Robert Allan JamesandClaude Sonnet 5 f37aa0fb17 Channel-open policy hook -- FABRIC-3.6.md task 3.7
Added HERMES-CHANNEL-OPEN? ( req-hi req-lo -- allow? ) at
capsules/ACL.4th block 4008 (default: approve everything) -- the one
word policy authors edit. sk_hermes_channel_open_policy(VM*, VMUuid)
(kernel_hermes.h/.c) is the C-side query that calls it via plain
word-dispatch against the target VM's own dictionary/stack, never
vm_interpret() (avoids task 3.4's input-buffer cursor hazard entirely)
and never decides the answer itself. Fails closed: no policy word,
a policy error, or stack underflow all deny, matching CLAUDE.md's
posture that absence of policy must never mean "always allow."

Two real bugs found and fixed before this was called done:
missing current_executing_entry assignment before calling the word's
func pointer (colon words silently no-op without it, vm_core.c:730 --
no crash, just a wrong answer); and a second FAIL with debug
instrumentation still in place whose precise cause isn't
reconstructable, since no intermediate commit exists for that attempt.

Self-test proves the task's check four ways against the same
unchanged C function: default approve, live redefinition to deny
(zero C change), restore, and a VM with no ACL.4th loaded at all
(fail closed). A fifth check wires the result into task 3.6's
sk_hermes_channel_respond() end to end: a denied policy produces a
NACK and no channel, ledger/stadium_conserved() holding throughout.

Scope, per Captain Bob's ruling: closes with the query built and
proven; sk_hermes_channel_respond() still takes a caller-supplied
approved bool rather than calling the policy internally. Wiring a
real channel-open call site to only this query is deferred to
whichever later task first needs a live decision.

dict_hash identical across amd64/aarch64/riscv64 for every VM, zero
UNKNOWN WORD, mkcapsule --lint clean (38 files, 0 violations).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 09:08:48 -04:00
Robert Allan JamesandClaude Sonnet 5 205a49ecd0 ACK/NACK and private-channel negotiation -- FABRIC-3.6.md task 3.6
Extracted sk_hermes_send_one() from sk_hermes_publish()'s own
per-subscriber body -- one code path for both point-to-point and
fan-out delivery, so the ledger can never diverge between them.
Point-to-point addressing turned out to be load-bearing, not
incidental: sk_hermes_publish()'s fan-out sets msg->to to whichever
member it is iterating, so a negotiation message "published" to the
common channel would spuriously reach every member, not just the real
target (checked with advisor() before building the naive version).
"Over the common channel" means every VM is reachable from birth (task
3.2), not that the exchange itself fans out -- messaging.4th's own
CH-REQUEST carried an explicit `to` for the same reason.

sk_hermes_channel_request/respond/close build the mechanics: request ->
grant (creates a private channel, subscribes both parties, sends
CH_GRANT + one ACK) or NACK ("a deny is a NACK", SXLV.1 -- no separate
type); close authorized by membership alone. The grant/deny decision is
a plain caller-supplied `approved` bool -- task 3.7 replaces the call
site that produces it with a real ACL.4th query, not this signature.

Self-test covers the task's own three checks plus a sibling advisor()
flagged: an approved respond() whose channel creation itself fails
(table exhausted) must still fall through to NACK, not a silent false
grant or half-open channel -- verified by exhausting the whole channel
table and confirming the fallback.

Bug found and fixed before this was called done: the first draft
dropped a message via pending_pop() alone, without releasing it first,
leaking its Stadium heat and failing the self-test's own ledger
baseline check (logs/20260922-065946/amd64/, kept as audit trail).
Fixed and re-verified PASS on all three architectures.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 07:25:51 -04:00
Robert Allan JamesandClaude Sonnet 5 8a2ee0fdad Payload bound and chunking -- FABRIC-3.6.md task 3.5
sk_hermes_publish() now enforces SK_HERMES_CHUNK_MAX_PAYLOAD (1024) on
every message's payload_len uniformly, chunked or not -- closing the
gap task 3.3 explicitly parked. A chunk carrier is
[SkHermesChunkHeader][content slice], slice capped at
1024 - sizeof(header) rather than 1024 itself, so every message on the
wire satisfies the same one-block bound vm_interpret()'s own drain
limit already requires -- a future chunk-aware drain never has to
special-case a carrier that can't be handed to vm_interpret() as-is.

Deliberately no chunking-sender API: building one would need
kernel-Hermes to own chunk-buffer memory with a real lifetime it has no
way to track (kept alive until every subscriber drains it). Sending is
a loop pattern a caller writes with sk_hermes_chunk_count() +
sk_hermes_publish(), demonstrated by this task's own self-test.
sk_hermes_reassemble() is pure and memory-agnostic: validates msg_id
agreement, exact seq coverage, and per-chunk slice sizes before a
single memcpy, with the total length computed once and checked against
the caller's buffer once -- never order-dependent on which chunk
happens to overflow.

Verified live on all three architectures: a 1024-byte payload as one
message, a 1025-byte send refused outright with the ledger untouched,
and a 3000-byte payload split into 3 chunks, drained, and reassembled
byte-exact against the original. dict_hash unmoved and identical
across architectures.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 06:51:48 -04:00
Robert Allan JamesandClaude Sonnet 5 2b1ba031a5 Drain at the outermost checkpoint -- FABRIC-3.6.md task 3.4
sk_hermes_drain_checkpoint() interprets one queued payload per checkpoint
(ruled: one message per checkpoint), reusing sk_vm_at_outermost_interpret()
and placed before the switch-signal block in vm_core.c's existing
cooperative checkpoint (sk_vm_context_switch() doesn't return until
switched back to, so drain must come first or it silently never runs on
a switching checkpoint).

Amends FABRIC-3.5.md SXLIII.5, caught by advisor() before writing the
naive version: "recursive drain is prevented for free" via
g_vm_interpret_depth is true but only for same-message re-drain -- it
doesn't cover the separate same-VM reentrancy hazard FABRIC-3.md SXX
already named for Hera specifically (VMCallState saves rsp/exit_colon/
ecw_nesting only, never input_buffer/input_length/input_pos). Draining
calls vm_interpret() on the same vm whose own vm_interpret() call is
still paused mid-word at the checkpoint; without saving and restoring
the cursor by hand, the enclosing REPL line or LOAD block would be
silently truncated. sk_hermes_drain_checkpoint() snapshots and restores
input_buffer/input_length/input_pos/mode/error/abort_requested around
the call. Not a divergence from the ruling -- cursor preservation is the
implementer's own obligation inside the ruled mechanism.

Gated behind a system-wide pending-total counter so the common
no-message-in-flight case costs one integer read per word dispatch, not
a stadium_max_vm_count()-sized queue scan (also flagged by advisor() as
a real hot-path cost, not deferred).

Verified live on all three architectures: a self-test publishes a real
payload to Hermes, proves the depth gate via VM-EXEC-ing an existing
harmless colon word into Hermes (genuine nested vm_interpret(), depth 2,
must not drain), then drains directly from genuinely-outermost context
and confirms exactly one clean drain. dict_hash unmoved and identical
across architectures.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 01:27:11 -04:00
Robert Allan JamesandClaude Sonnet 5 1f6343bc03 Publish path, no dispatch -- FABRIC-3.6.md task 3.3
sk_hermes_publish() allocates one SkHermesMessage per channel member
(heat-cost ruling 2026-09-21: one message per subscriber, funded by
the publisher's own reservoir) and enqueues each onto a new
per-subscriber SkHermesPendingQueue -- found-or-created lazily by
vm_id, sized from stadium_max_vm_count() like the channel/switch
tables. Best-effort across subscribers: a failed allocation or full
queue skips and rolls back just that one subscriber, not the whole
publish -- the natural reading of "ledger and stadium_conserved() hold
across N publishes to M subscribers" (the task's own check), not a
separate ruling.

Dispatches nothing -- sk_hermes_pending_count()/peek()/pop() are the
read/drain primitives task 3.4's real checkpoint-driven drain will
build on; this task's own self-test uses them directly since no
checkpoint hook exists yet.

Verified live on all three architectures: pending-queue table sized
50/202/50 slots (tracking the channel table's own per-arch sizing), a
synthetic publish self-test (2 publishes to 3 subscribers) confirms
exact per-subscriber delivery counts, and ledger/stadium_conserved()
invariants hold both mid-publish and after manually draining every
queue back to baseline.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 00:49:22 -04:00
Robert Allan JamesandClaude Sonnet 5 a4afdfa591 Channel table + common channel, inert (B1) -- FABRIC-3.6.md task 3.2
Adds SkHermesChannel: a channel is an index into a boot-time,
stadium_max_vm_count()-sized table (same sizing pattern task 3.1
established for the switch table -- no separate numeric rule was ruled
for this table, so task 3.1's bound is extended directly, flagged as
such rather than restated as a new ruling). No name field, mirroring
messaging.4th's own nameless CH-ARENA.

The common channel (index 0) is created at boot and permanent. Hera
subscribes explicitly in kernel_main.c (she is the one VM never born
through capsule_birth_baby()); every other VM -- Tripod fleet and
future WIREBIND identities alike -- subscribes inside
capsule_birth_baby() itself, the single choke point every other birth
already passes through.

Inert: no publish, no dispatch, no ACK/NACK, no ACL hook (tasks 3.3,
3.6, 3.7). Verified live on all three architectures: channel table
sized to 50/202/50 slots (matching switch-signal's own per-arch
sizing), common-channel fleet self-test confirms all four Tripod
members are members, and a synthetic create/subscribe/unsubscribe/
destroy round-trip against a private topic passes, including refusing
to destroy the common channel.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 00:25:13 -04:00
Robert Allan JamesandClaude Sonnet 5 c19ef365fe Dynamic switch table (B2) -- FABRIC-3.6.md task 3.1
Replaces the fixed SK_SWITCH_MAX_SLOTS=16 compile-time array with a
boot-time, RAM-derived allocation via a new sk_vm_switch_signal_boot_init(),
kmalloc'd to stadium_max_vm_count() entries -- the same pattern
session_boot_init() already established for Stadium-derived sizing.
Every switch-signal participant is a Stadium VM, so this reuses that
bound directly rather than deriving a separate one.

Verified live on all three architectures: switch table sized to 50
slots (amd64), 202 slots (aarch64), 50 slots (riscv64) -- all well
past the old fixed cap. All three boot to [zuse@Hera] ok> cleanly;
dict_hash for Hermes/Hestia identical across architectures, unmoved
from pre-task values.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 23:59:50 -04:00
Robert Allan JamesandClaude Sonnet 5 66ea4a5e74 Record task 3.0 rulings (FABRIC-3.5.md §XLVI); close FABRIC-3.6.md task 3.0
Captain Bob ruled all five §XLV.4 sub-items plus task 3.3's heat-cost
design point: ACK on channel-open+delivery only; the channel-open ACL
hook is a new word in ACL.4th; switch-table sizing mirrors Stadium's
stadium_max_vm_count_val; chunks carry (msg_id, seq, is_last); drain
one message per outermost-interpret checkpoint; publish costs one
message per subscriber. Tasks 3.1-3.7 may now be written precisely.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 23:29:12 -04:00
Robert Allan JamesandClaude Sonnet 5 f2f7111dff Draft Phase 3 task breakdown (3.0-3.11), awaiting review -- FABRIC-3.6.md
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 10:49:10 -04:00
Robert Allan JamesandClaude Sonnet 5 303b0c7edf Record B1/B2/B4 rulings (FABRIC-3.5.md §XLV); clear Phase 3 blockers in FABRIC-3.6.md
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 08:55:21 -04:00
Robert Allan JamesandClaude Sonnet 5 feace42397 Add diagnostic scan cross-check of the Hermes counters -- FABRIC-3.6.md task 2.8
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 08:46:07 -04:00
Robert Allan JamesandClaude Sonnet 5 2a2bf6eb35 Stage B proof: add per-VM consumed term to stadium_conserved -- FABRIC-3.6.md task 2.7
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-20 21:31:45 -04:00
Robert Allan JamesandClaude Sonnet 5 313ffc89e6 Add exact-equality Hermes ledger self-audit -- FABRIC-3.6.md task 2.6
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-20 21:02:27 -04:00
Robert Allan JamesandClaude Sonnet 5 d11eb2e5db Add sk_hermes_decay(), ledgering decay into consumed -- FABRIC-3.6.md task 2.5
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-20 18:48:46 -04:00
Robert Allan JamesandClaude Sonnet 5 493d410028 Add the four ledger counters (held/pulled/returned/consumed) -- FABRIC-3.6.md task 2.4
FABRIC-3.5.md SXL.4's ledger: held == pulled - returned - consumed,
epsilon zero. Added sk_hermes_ledger() (an accessor, not a mutator)
plus four static counters in kernel_hermes.c.

Each counter has exactly one increment/decrement site: held/pulled
both move at sk_hermes_alloc()'s single success path, after every
refusal branch has already returned; held/returned both move at
sk_hermes_release()'s single success path. consumed is declared and
always reads 0 -- its one increment site doesn't exist yet, and won't
until task 2.5 gives decay something to record.

sk_hermes_release() now reads the Stadium cell's live header.heat
immediately before calling stadium_evict(), rather than assuming the
original pulled amount -- stadium_evict() zeroes the header as part of
freeing the cell and its own return value is a success code, not the
credited amount, so this is the only point the true remaining heat is
available. Today this always equals the original Q.SLOT pull; once
task 2.5's decay exists, this is what keeps returned correct without
touching this function again.

Self-test (kernel_main.c) extended: snapshots the ledger before
running so it checks its own deltas, verifies held/pulled grow by
exactly got_n * Q_SLOT on allocation with returned/consumed untouched,
then verifies held returns to its starting value and returned grows by
the same amount on release, and checks the audit invariant itself as a
bonus (task 2.6 formalizes this properly).

Three-arch boot clean: amd64/aarch64/riscv64 all reach [zuse@Hera] ok>,
zero UNKNOWN WORD, dict_hash unmoved. All three print PASS with
identical final ledger: held=0 pulled=65536 returned=65536 consumed=0.
No compiler warnings.

Authorized by Captain Bob ("keep going with rhe 6.5 document").

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-20 05:59:09 -04:00
Robert Allan JamesandClaude Sonnet 5 2c1dc1753a Correct sk_hermes_alloc() to admit a real Stadium patron; add sk_hermes_release() -- FABRIC-3.6.md tasks 2.2 (amended) + 2.3
Real finding, caught before building release on a foundation that
couldn't support it: task 2.2's first cut of sk_hermes_alloc() pulled
reservoir heat but never admitted a real Stadium-floor patron -- just a
local in_use flag. FABRIC-3.5.md SXXXIII.4 item 1 says MSG-FREE-NODE
returns heat "via STADIUM-EVICT", which only means something if
allocation admitted something. SXL.4's own invariant, Sigma(resident
patron heat) + reservoir + consumed == Q48_ONE, cannot balance if held
heat is invisible to every term while held. Flagged to Captain Bob
before proceeding; authorized to correct 2.2 in the same pass as
building 2.3 on top of the fix.

sk_hermes_alloc() now calls stadium_admit() with heat = the pulled
amount, behaviour = STADIUM_BEHAVIOUR_DELIVER (matching messaging.4th's
own SB-DELIVER STADIUM-ADMIT exactly), and identity = the message's own
slot index (matching the FORTH precedent -- caught live in the first
boot of this fix that omitting this made stadium_dispatch()'s existing
DELIVER diagnostic print msg_idx=0 for every message instead of a
distinct value). The returned cell index is stored in the message's
own stadium_cell field. Stadium-floor refusal (independent of reservoir
affordability) rolls back the pull the same way the other refusal
paths already do.

sk_hermes_release() -- the function task 2.3 actually asks for -- calls
stadium_evict() on that cell, which itself returns the departing
patron's remaining heat to its owning VM's reservoir, matching
MSG-FREE-NODE's exact shape. Release does not touch the reservoir
directly.

Self-test (kernel_main.c) extended: keeps every allocated message's
pointer, allocates to exhaustion as before, releases all of them, and
checks the reservoir returns to precisely its starting value.
"Undecayed" is true by construction (no decay/TTL logic exists yet,
task 2.5) -- exact restoration, not approximate.

Three-arch boot clean: amd64/aarch64/riscv64 all reach [zuse@Hera] ok>,
zero UNKNOWN WORD, dict_hash unmoved from task 2.2. All three print
identical PASS arithmetic: reservoir0=65536, reservoir_after_alloc=0,
reservoir_final=65536. No compiler warnings.

stadium_dispatch()'s DELIVER-case console output (one line per
eviction) is pre-existing instrumentation, not new -- confirmed real
and load-bearing per stadium.c's own comment, verbose but expected.

Authorized by Captain Bob ("yes").

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-20 04:20:14 -04:00
Robert Allan JamesandClaude Sonnet 5 9d129fdb1c Add sk_hermes_alloc(), the heat-coupled allocator -- FABRIC-3.6.md task 2.2, item 28
The piece FABRIC-3.5.md SXXXIII.6 calls "what remains genuinely hard,"
built and proven first per its own recommendation. Added
src/starkernel/vm/kernel_hermes.c (wired into Makefile.starkernel's
LOADER_EXTRA_SRCS -- this repo lists vm/*.c files explicitly, no glob)
and sk_hermes_alloc()'s declaration in kernel_hermes.h.

Checks stadium_reservoir_peek(vm_id) >= SK_HERMES_Q_SLOT before
touching the reservoir at all -- refusal this way needs no rollback,
since nothing was pulled -- with an explicit rollback path
(stadium_reservoir_push) kept defensively for the pull-then-short case,
though nothing in this single-core kernel is expected to reach it.

SK_HERMES_Q_SLOT = Q48_ONE / SK_HERMES_MSG_MAX (2048), deliberately
simpler than messaging.4th's own formula, which reserves a Q.1/3 floor
for COMMON-CH's own Stadium heat -- kernel-Hermes has no such object
(SXXXIII.4/SXXXIII.5's flat membership list carries no heat of its
own), so there is nothing left for that floor to protect.

Self-test in kernel_main.c, same diagnostic-only synthetic-VM pattern
as the existing Stadium quota grant self-test (lo=3, distinct from
that test's lo=1): reads back the actual granted reservoir rather than
assuming a number, derives expected_n from it, allocates to refusal,
and checks the refusal lands at exactly expected_n, the reservoir
doesn't move on the refused attempt (rollback proven, not assumed),
and the final reservoir is exactly reservoir0 minus got_n times
Q_SLOT.

Three-arch boot clean: amd64/aarch64/riscv64 all reach [zuse@Hera] ok>,
zero UNKNOWN WORD, dict_hash unmoved from task 2.1 (pure C, no FORTH
touched). All three print identical self-test arithmetic: reservoir0=
65536 Q_SLOT=2048 expected_n=32 got_n=32 reservoir_after=0. No compiler
warnings.

Noted, not fixed: Q_SLOT's divisor and SK_HERMES_MSG_MAX are the same
32, so reservoir and arena exhaustion land at exactly the same count by
construction -- this test can't distinguish which refusal reason
fired, only that refusal is correct and rolls back correctly.

Deliberately not evidence for stadium_conserved(): allocating alone
(no release yet, task 2.3) leaves pulled heat held off the Stadium
floor, so the two-term check would correctly read false right now if
run mid-hold. That's expected, not a bug -- Stage B (task 2.7) is
defined as "before and after the alloc/free cycle," not "continuously
during." This task's self-test checks reservoir arithmetic directly
instead.

Authorized by Captain Bob ("Yes continue").

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 21:00:20 -04:00
Robert Allan JamesandClaude Sonnet 5 f10fa7ae83 Add kernel-Hermes message/membership structures -- FABRIC-3.6.md task 2.1, Phase 2 begins
Phase 2, task 2.1 only: type definitions, wired to nothing, drawing no
heat -- no allocator, no protocol logic, no registration anywhere.
FABRIC-3.5.md SXXII.4: Phase 2 structures come first and prove nothing
until the allocator is built on top (task 2.2 onward, each its own
commit).

Added include/starkernel/vm/kernel_hermes.h:

SkHermesMessage -- field-for-field mirror of messaging.4th's live
9-cell MSG-* layout (type/from/to/payload addr+len/Stadium cell
index/seq/channel/orig-type), per SXXXIII.4 item 1 ("roughly half the
file is accessors that become struct fields"), plus an explicit
in_use flag for task 2.2's allocator. Deliberately no separate heat
field: per SXL.4, a message's heat IS the Stadium cell it occupies,
not a value copied alongside it -- one source of truth for the
conservation invariant stadium_conserved() (task 0.7) checks.

SkHermesMembership -- one flat broadcast membership list, SXXXIII.4/
SXXXIII.5's recommended replacement for messaging.4th's 28-word channel
abstraction (traced to exactly one live caller, CH-ADD-MBR). Item 27
(negotiation vs. broadcast, Phase 3 blocker B1) is not answered by this
structure and isn't meant to be -- a flat list is correct either way.

Genuinely wired to nothing: no .c file, no Makefile change, no include
from any compiled source. Syntax-checked standalone (gcc -std=c99
-Wall -Wextra -Werror -fsyntax-only) before touching the real build.

Boot byte-identical to task 1.9's baseline on amd64 (same dict_hash
triple, zero UNKNOWN WORD). Did not repeat aarch64/riscv64 -- the file
compiles into no object on any architecture, so there is no mechanism
by which it could diverge.

Authorized by Captain Bob ("yes").

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 20:48:24 -04:00
Robert Allan JamesandClaude Sonnet 5 1e75bb8039 Assert Hestia's headless invariant -- FABRIC-3.6.md task 1.9, Phase 1 closed
Audited first: neither capsules/hestia/init.4th nor her birth block in
kernel_main.c references g_wirebind_attached_username, CONSOLE-ATTACH,
MINT, or any proxy-minting mechanism -- the invariant already held
structurally, by absence. Stated it explicitly anyway, per the task:
added a comment at Hestia's birth site quoting FABRIC-3.5.md SXVIII.6's
invariant verbatim, warning future edits not to add console/wirebind/
proxy code there without re-reading it first.

Verified live with the actual no-thumbdrive boot
(ARCH=<arch> qemu ZUSEDISK=), not the default. All four VMs born
successfully on all three architectures, zero UNKNOWN WORD, and zero
ok> occurrences anywhere in any of the three full logs -- genuinely
silent, matching sk_repl_headless_wait()'s own documented "no banner,
no prompt, no input surface at all." Watched each log's line count
post-birth for 8-10s to confirm it stayed flat rather than eventually
printing something late.

Confirmed no regression on the standard (with-thumbdrive) path on
amd64: dict_hash identical to task 1.8's baseline. Did not repeat that
check on aarch64/riscv64 -- the change is a comment only, cannot
diverge by compiler, and the headless invariant itself was already
proven identically on all three.

Phase 1 is now fully closed (tasks 1.1-1.9). Tripod is Hera/Artemis/
Hestia plus Hermes (retained through Phase 1-3 per SXXXIV.4); Hestia
owns the drawing fabric exclusively; headless-until-login intact with
Hestia in the fleet. Phase 2 is next.

Authorized by Captain Bob ("yes").

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 17:46:32 -04:00
Robert Allan JamesandClaude Sonnet 5 6c6293cd52 Move PLOT/FB-WIDTH/FB-HEIGHT registration to Hestia only -- FABRIC-3.6.md task 1.8
Real fix for task 0.8's finding. register_framebuffer_words() was
called unconditionally from register_forth79_words() (word_registry.c),
itself called unconditionally from vm_init_with_host() -- the generic
per-VM bootstrap every VM goes through, with no way to know a VM's
name at that point.

Removed the unconditional call. Added a name-gated call instead in
capsule_birth_baby() (capsule_birth.c), beside the existing
is_fleet_foundation check -- the one place in the birth path where
capsule_name and the newly-allocated VM* are both in scope together:
Hestia gets register_framebuffer_words(), nobody else does.

Positively verified live, exactly as the task's own check demands:
FB-WIDTH via VM-EXEC returns UNKNOWN WORD in Hermes and Artemis, 1280
in Hestia. Confirmed at the fundamental level too: Hera's own base
PARITY:M7.1a word count dropped from 531 to 528 -- exactly the three
words removed -- identical across all three architectures.

Three-arch boot clean: amd64/aarch64/riscv64 all reach [zuse@Hera] ok>,
zero UNKNOWN WORD during boot. mkcapsule --lint capsules/ clean, 38
files / 0 violations (pure C change, no capsule content touched). No
compiler warnings.

Side effect on the vendored hosted build, expected and not a
regression: capsule_birth.c is kernel-only, so the hosted starforth
binary has no Hestia concept and now never registers these words at
all -- confirmed live. framebuffer_words.c's own top comment already
calls this surface "kernel-only, no-op on hosted builds," so the prior
stub registration was already vestigial. Ran a plain `make` sanity
build per CLAUDE.md's own stated purpose for that target; regenerated
lfs/amd64/starforth included here rather than left stale.

Authorized by Captain Bob ("yes").

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 17:36:18 -04:00
Robert Allan JamesandClaude Sonnet 5 5afb33049f Move fabric.4th + font.4th to hestia/init.4th -- FABRIC-3.6.md tasks 1.6+1.7 (merged)
Tasks 1.6 and 1.7 are not independent, and the punchlist's split was
wrong: font.4th calls G-LINE/G-ELLIPSE, which are fabric.4th's own
words. Confirmed live before committing to an approach -- removed only
fabric.4th's EXEC from init.4th, left font.4th's in place, booted
amd64: Hera's boot floods with UNKNOWN WORD: 'G-LINE'/'G-ELLIPSE' the
moment font.4th loads (logs/20260919-171047/amd64/, kept as evidence).
Reverted that partial state, asked Captain Bob how to proceed given
neither task can independently pass its own three-arch-boot check, and
was told to use best practices.

Moved both together, in their original relative order, into a new
capsules/hestia/init.4th block 4988 -- a deliberate, documented
deviation from "one task, one commit," not a bundling of convenience.

Three-arch boot clean: amd64/aarch64/riscv64 all reach [zuse@Hera] ok>,
zero UNKNOWN WORD. Hestia's dict_hash identical across all three
architectures. Verified the shrink/grow live, not just inferred from
hash movement: HERE reads 20008 in Hera, 63040 in Hestia post-move.
CART-PLOT in Hera is UNKNOWN WORD; the identical call routed into
Hestia via VM-EXEC reaches the word and fails on a stack underflow
instead, proof it exists there since an unknown word can't underflow.

mkcapsule --lint capsules/ clean, 38 files / 0 violations. MANIFEST.md
updated: init.4th's block 2049 entry no longer lists fabric.4th/
font.4th; hestia/init.4th's entry gains block 4988.

Authorized by Captain Bob ("Use best practices.").

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 17:25:23 -04:00
Robert Allan JamesandClaude Sonnet 5 487769e18a Register Hestia for switch signals -- FABRIC-3.6.md task 1.5
Added a fourth capsule_vm_find_by_name_nocase("Hestia", ...) +
sk_vm_switch_signal_register(...) block in src/starkernel/kernel_main.c,
same shape as the existing Hermes/Artemis blocks, placed after all four
fleet members are confirmed born -- the existing comment on this block
already states why: no critical-section protection during setup, so
registering earlier risks the signal firing mid-birth.

Three-arch boot clean: amd64/aarch64/riscv64 all reach [zuse@Hera] ok>,
zero UNKNOWN WORD, hashes identical to task 1.4's baseline (this is
pure C runtime state, doesn't touch any FORTH dictionary). No compiler
warnings.

Took FABRIC-3.5.md SXXXV.0's "invisible by default" warning literally
rather than trusting a clean boot log alone: SXXVIII.2's own recorded
switch-storm signature is "QEMU pinned near 100% CPU, serial log frozen
solid," not an error message. Confirmed normal wall-clock boot time on
all three (~30s) and, since TCG itself always shows ~100% CPU
regardless of guest workload, watched each serial log's line count at
the idle prompt for 5-10s and confirmed it stopped growing rather than
flooding or silently stalling.

Authorized by Captain Bob ("yes").

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 17:07:19 -04:00
Robert Allan JamesandClaude Sonnet 5 10e2d654e5 Birth Hestia in kernel_main.c -- FABRIC-3.6.md task 1.4
Added a birth block immediately after Hermes's own, same shape:
S" Hestia" BIRTH followed by a registry-lookup confirmation. Fleet is
now Hera/Hermes/Artemis/Hestia, four VMs, through Phase 1-3
(FABRIC-3.5.md SXXXIV.4) until Phase 4 retires Hermes.

Three-arch boot clean: amd64/aarch64/riscv64 all reach [zuse@Hera] ok>,
zero UNKNOWN WORD. Registry shows all four (BIRTH: Hermes live, BIRTH:
Hestia live, PARITY:BIRTH for all three non-Hera VMs). Hestia's
dict_hash identical across all three architectures (0x31cab513929eea89).
Hera/Hermes hashes unchanged from task 1.3; Artemis's vm_id shifted
(now the 4th birth instead of 3rd -- sequence-derived, not identity-
derived, so expected) but its dict_hash is unchanged and still
identical across arches. No compiler warnings.

Noted, not a regression: Hestia's birth log shows the same
"( Unterminated comment" HADES warning Artemis's birth has shown since
task 0.0's first baseline.

Authorized by Captain Bob ("yes").

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 17:01:05 -04:00
Robert Allan JamesandClaude Sonnet 5 ff00de9a63 Add Hestia to is_fleet_foundation -- FABRIC-3.6.md task 1.3
Fourth vm_name_prefix_eq_nocase(capsule_name, "Hestia") check alongside
Hera/Hermes/Artemis in src/starkernel/capsule/capsule_birth.c's
is_fleet_foundation local -- Hermes retained, per FABRIC-3.5.md
SXXXIV.4 (he stays live and fleet-foundation through Phase 1-3). The
flag's only consequence is session_set_pinned(vm_id, 1) for whichever
VM name matches.

Three-arch boot clean: amd64/aarch64/riscv64 all reach [zuse@Hera] ok>,
zero UNKNOWN WORD, byte-identical to the pre-change baseline -- expected,
since nothing births anything named "Hestia" yet (task 1.4), so the
added name never matches. Hermes confirmed still present in the check
and still born normally in all three logs.

Authorized by Captain Bob ("yes").

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 16:53:40 -04:00
Robert Allan JamesandClaude Sonnet 5 4f7cbe78fc Create capsules/hestia/init.4th -- FABRIC-3.6.md task 1.2
The third Tripod leg's first real file (FABRIC-3.5.md SII/SIV). Blocks
4986-4987 of the 4986-4996 allocated in task 1.1 (b624133a): WELCOME
banner, then common:messaging.4th load + MSG-CD-INIT + COMMON-CH join
on slot 11, modeled on hermes/init.4th's equivalent shape. No fabric.4th/
font.4th yet -- tasks 1.6/1.7. Not yet birthed -- task 1.4.

Slot 11 is a fresh routing-table slot, not Hermes's slot 1: Hera/
Hermes/Artemis hold 0/1/2 and identities hold 3-10 (messaging.4th:
78-85), and Hermes stays live and fleet-foundation through Phase 1-3
(SXXXIV.4) so his slot isn't free yet.

Deliberately did not add a VM-NAME-REG entry for "Hestia" to
messaging.4th's VM-NAMES-INIT -- Phase 1's own gate is "messaging
untouched" and that table lives in messaging.4th. Consequence: once
birthed, Hestia is COMMON-CH-reachable by raw slot but not yet
VM-EXEC-addressable by name. Flagged in FABRIC-3.6.md rather than
decided -- whichever task first needs name lookup settles where that
one-line addition goes, and it will need its own authorization since
it touches a file Phase 1 promised not to.

mkcapsule --lint capsules/ clean, 38 files / 0 violations. MANIFEST.md
given a matching entry. Three-arch boot clean: amd64/aarch64/riscv64
all reach [zuse@Hera] ok>, zero UNKNOWN WORD, byte-identical to task
0.7's baseline (same dict_hash triple) -- exactly as expected, since an
unbirthed capsule is inert.

Authorized by Captain Bob ("YES").

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 16:31:59 -04:00
Robert Allan JamesandClaude Sonnet 5 b624133a28 capsules/MANIFEST.md: allocate Hestia's block range 4986-4996 -- FABRIC-3.6.md task 1.1
Documentation only, no capsule file created yet (that's task 1.2).
Checked against actual current occupancy rather than trusting
MANIFEST.md's own stale blanket "4853+ OPEN" line: fabric.4th already
occupies 4900-4924 and font.4th 4925-4985 (both standalone capsule
files EXEC'd by init.4th, block-numbered independently of it -- tasks
1.6/1.7 relocate which capsule EXECs them, not their own ranges), and
4997 is the console proxy's hardcoded Block 4997 string literal
(capsule_console.c:27-29). Allocated 4986-4996, the gap between the
two, avoiding 4997 as the task requires.

Split the Unassigned Ranges table's single blanket line into an
explicit claim for 4986-4996 plus a corrected "OPEN" line that excludes
the ranges actually in use. Recorded in MANIFEST.md rather than
tools/capsule-reserved.txt -- that file is for blocks owned by
non-capsule infrastructure per its own header comment; a real capsule
allocation belongs in MANIFEST.md alongside every other infrastructure
capsule's entry.

mkcapsule --lint capsules/ clean, 37 files / 0 violations, unchanged.

Authorized by Captain Bob ("YES").

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 16:22:03 -04:00
Robert Allan JamesandClaude Sonnet 5 eed2a9dfb5 FABRIC-3.6.md task 0.8: PLOT/FB-WIDTH/FB-HEIGHT reachability audit -- Phase 0 closed
Read-only audit, no code changed. Finding: reachable from every VM
today, not confined to one table, contrary to item 33's premise.

FORTH level matches expectation: fabric.4th/font.4th are EXEC'd only
from capsules/init.4th (Hera). C level does not: register_framebuffer_
words() (src/word_source/framebuffer_words.c:60-65) is called
unconditionally from register_forth79_words() (src/word_registry.c:
139), itself called unconditionally from vm_init()
(src/starkernel/vm/vm_bootstrap.c:263) -- the generic per-VM bootstrap
every VM goes through, no identity check.

Verified live rather than trusting the source trace alone: booted
amd64 and ran `S" FB-WIDTH ." S" Hermes" VM-EXEC` and the same against
Artemis -- both returned 1280, not UNKNOWN WORD. Neither loads
fabric.4th, so the raw C primitive itself is answering.

Not fixed here, per the task's own read-only scope. Gives task 1.8 a
concrete starting state: its own check ("a non-Hestia VM calling PLOT
gets UNKNOWN WORD") currently fails, and register_framebuffer_words()'s
call site will need to become conditional or move out of the universal
bootstrap -- not just the FORTH-level relocation tasks 1.6/1.7 already
plan for.

Phase 0 gate met across tasks 0.2-0.7 (three-arch boot, stadium_
conserved() true, zero UNKNOWN WORD, repeatedly). Phase 0 is closed;
Phase 1 (Hestia, messaging untouched) is next.

Authorized by Captain Bob ("Continue.").

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 14:04:50 -04:00
Robert Allan JamesandClaude Sonnet 5 380f0a09c9 Add stadium_conserved() -- FABRIC-3.6.md task 0.7, item 41
Boolean analogue of vm_physics_conserved(), for the Stadium per-VM
quota invariant rather than fleet-wide execution heat
(FABRIC-3.5.md SXXXIX.4). int stadium_conserved(VMUuid vm_id), in
src/starkernel/vm/stadium.c alongside stadium_resident_sum()/
stadium_reservoir_peek() that it's built from, declared in
include/starkernel/vm/stadium.h.

Implements the two-term form -- resident_sum(vm_id) +
reservoir_peek(vm_id) == Q48_ONE -- not the three-term form SXL.4
rules for the eventual system. That ruling's `consumed` term is a
Phase 2 kernel-Hermes ledger deliverable that doesn't exist yet:
nothing draws on any VM's Stadium quota today (task 2.2 is literally
where that wiring gets built), so consumed is honestly zero right now.
Folding it in as a placeholder would be inventing Phase 2 state ahead
of it existing -- the doc comment says so explicitly, so whoever
builds Phase 2's ledger extends this function rather than working
around it.

Wired into the existing per-VM boot diagnostic
(stadium_words_print_boot_diagnostics(), kernel_main.c:810, Hera
only -- the sole existing call site) rather than adding a new one,
printing CONSERVED/DRIFTED the same shape vm_physics_status() already
uses.

Three-arch boot clean: amd64/aarch64/riscv64 all reach [zuse@Hera] ok>,
zero UNKNOWN WORD, all print "Stadium conservation: CONSERVED" with
identical resident_sum=47641 reservoir=17895 sum=65536=Q48_ONE. No
compiler warnings on either edited file (forced recompile checked).

Authorized by Captain Bob ("Yes.").

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 13:56:14 -04:00
Robert Allan JamesandClaude Sonnet 5 9886ad5315 tools/capsule-reserved.txt: return freed block ranges -- FABRIC-3.6.md task 0.6
Added 4055-4059 (former common/msg.4th) and 4300-4399 (former
process.4th) as reserved, each noted as freed by this reshuffle's
strip rather than owned by non-capsule infrastructure -- the file's
usual purpose (Artemis's own block usage, etc.). Framed explicitly as
lifted, not permanent, once someone deliberately wants a range back,
per FABRIC-3.5.md SSXVIII.4/XXII.4: freed ranges should be returned
here rather than silently available for a future capsule to reclaim
without anyone noticing.

mkcapsule --lint capsules/ clean, 37 files / 0 violations.
check_reserved_conflicts() -- the hard build-gate that actually reads
this file (tools/mkcapsule.c:974) -- passes clean on a real
`make -f Makefile.starkernel ARCH=amd64 all`, confirming the new
entries don't collide with anything currently baked. Registry/
documentation only, no capsule content touched, so no 3-arch boot run
for this task.

All of Phase 0's strips and documentation corrections (0.1-0.6) are
now done. stadium_conserved() (0.7) and the PLOT/FB-WIDTH/FB-HEIGHT
registration audit (0.8) remain.

Authorized by Captain Bob ("Go for it.").

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 13:45:01 -04:00
Robert Allan JamesandClaude Sonnet 5 e233d09fa1 capsules/MANIFEST.md: correct blocks 4055 and 2049 -- FABRIC-3.6.md task 0.5
Block 2049's justification claimed init.4th loads compudynamics,
common:msg, fleet-k and process. Read the live file: it loads none of
these. compudynamics.4th/fleet-k.4th were already deleted (9323f776,
2026-07-05); common:msg.4th/process.4th are this reshuffle's own
Category A strips (tasks 0.2/0.3, a0e97258/aafcce43) and were never
EXEC'd from this block even before that -- the manifest entry was
already wrong prior to this pass, just not yet caught.

Removed the standalone common/msg.4th (former block 4055) and
process.4th (former blocks 4300-4301) sections, since both files no
longer exist, and folded them into "Deleted capsules (historical)"
alongside the existing compudynamics.4th/fleet-k.4th entry -- same
convention, same section. Noted that common/msg.4th's own entry had
claimed it was an immutable ABI "every messaging VM loads at birth",
a claim FABRIC-2.md:2773 had already flagged stale before this
correction landed. Updated the Unassigned Ranges table so 4055-4059
and 4300-4399 read as former-file ranges rather than "extension space"
for files that no longer exist.

Documentation only -- no capsule content touched, mkcapsule --lint
capsules/ still clean (37 files, 0 violations). Verified every
remaining ### `*.4th` section in the manifest names a file actually
present on disk.

Authorized by Captain Bob ("Keep going.").

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 13:42:25 -04:00
Robert Allan JamesandClaude Sonnet 5 bcc678e354 Strip SPAWN-EVENT from messaging.4th -- FABRIC-3.6.md task 0.4
Dead per task 0.1's reachability check (156d1642): zero references by
name anywhere in the tree outside its own definition -- process.4th's
own SPAWN/PAUSE/RESUME/KILL-VM calls passed bare numeric literals
(1/2/3/4), never this constant's name. Removed the single line only;
PAUSE-EVENT/RESUME-EVENT/KILL-EVENT are left in place, matching the
punchlist's stated scope -- they are equally dead-by-name but that
widening was flagged in 0.1's findings, not folded into this task.

mkcapsule --lint capsules/ clean, 37 files / 0 violations, unchanged
(one-line edit, no file added or removed). Three-arch boot clean:
amd64/aarch64/riscv64 all reach [zuse@Hera] ok>, zero UNKNOWN WORD.
dict_hash moved for Hermes/Artemis (both load messaging.4th) and is
identical across all three architectures; Hera's PARITY:M7.1a snapshot
unchanged as in the prior two strips.

Phase 0's three strips (0.2-0.4) are now all done. capsule-reserved.txt
and MANIFEST.md's stale block-4055/2049 entries remain for 0.5/0.6.

Authorized by Captain Bob ("Yes, please continue.").

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 13:39:06 -04:00
Robert Allan JamesandClaude Sonnet 5 aafcce4320 Strip capsules/process.4th and its EVENT-EMIT/-WAIT/-DRAIN -- FABRIC-3.6.md task 0.3
process.4th is dead per task 0.1's reachability check (156d1642): zero
EXEC sites anywhere, and its four words (SPAWN/PAUSE/RESUME/KILL-VM)
have zero callers outside the file itself. Deleted outright.

EVENT-EMIT/EVENT-WAIT/EVENT-DRAIN (messaging.4th Block 5030) had
exactly one live caller -- process.4th, per FABRIC-3.5.md SXXXIII.3 --
so they go with it. Block 5030 was self-contained (nothing else in
it), so the whole block was removed rather than edited down.

mkcapsule --lint capsules/ clean, 37 files / 0 violations (was 38).
Three-arch boot clean: amd64/aarch64/riscv64 all reach [zuse@Hera] ok>,
zero UNKNOWN WORD. dict_hash moved for Hermes and Artemis (both load
messaging.4th) but is identical across all three architectures, which
is the invariant that matters, not an unchanging absolute value
(SXXII.4). Hera's own PARITY:M7.1a snapshot is unchanged since it fires
before any capsule loads.

capsule-reserved.txt and MANIFEST.md's stale block-4055/2049 entries
still untouched -- tasks 0.5 and 0.6, now unblocked since both strips
are done.

Authorized by Captain Bob ("Yes, you may continue.").

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 13:33:42 -04:00
Robert Allan JamesandClaude Sonnet 5 a0e9725883 Strip capsules/common/msg.4th -- FABRIC-3.6.md task 0.2
Dead per task 0.1's reachability check (156d1642): zero EXEC sites in
any boot-loaded capsule, and its only two words (HERMES-ACK/
HERMES-NACK) have zero callers anywhere -- messaging.4th's own block
5033 comment already says outright that this file's ACK/NACK
indirection is retired.

mkcapsule --lint capsules/ clean, 38 files / 0 violations (was 39).
Three-arch boot clean: amd64/aarch64/riscv64 all reach [zuse@Hera] ok>,
zero UNKNOWN WORD, dict_hash unchanged from the task 0.0 baseline on
all three -- expected, since the file was never loaded into any VM's
dictionary in the first place.

capsule-reserved.txt and MANIFEST.md's stale block-4055 entry are left
untouched here, per the punchlist's own ordering -- 0.6 returns the
freed block range and 0.5 corrects the manifest once 0.3's strip is
also done.

Authorized by Captain Bob ("Go ahead.").

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 13:24:03 -04:00
Robert Allan JamesandClaude Sonnet 5 156d1642de FABRIC-3.6.md task 0.1: Category A reachability established
Checked the three §XXII.2 routes for capsules/common/msg.4th,
capsules/process.4th, and the SPAWN-EVENT constant, never by grep
count alone: a boot-path trace of every capsule init.4th loads at
birth, a tools/experiments/docs invocation search, and confirmation
that mere presence in the baked capsule directory doesn't make a name
reachable if nothing constructs it at runtime. All three are dead by
every route -- zero EXEC sites, zero callers of their exported words.

Two findings recorded, neither changing the punchlist's plan:

tools/hermes_smoke.sh calls EVENT-EMIT/EVENT-WAIT/EVENT-DRAIN directly,
a caller FABRIC-3.5.md SXXXIII.3 missed when it said the only other
reference was MANIFEST.md. Doesn't make them live -- the script is
already broken on its own terms (references capsules/core/init.4th and
build/amd64/standard/starforth, neither exists; calls the pre-rename
CD-INIT word). Task 0.3's plan to strip these three words alongside
process.4th stands.

PAUSE-EVENT/RESUME-EVENT/KILL-EVENT are exactly as dead-by-name as
SPAWN-EVENT -- process.4th's own calls pass bare numeric literals, never
the constant names. Task 0.4 only names SPAWN-EVENT; flagged for
whoever picks up Category A stripping next rather than expanding this
task's scope.

Investigation only, no code changed.

Authorized by Captain Bob ("begin.").

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 13:18:09 -04:00
Robert Allan JamesandClaude Sonnet 5 4544877a36 FABRIC-3.6.md task 0.0: three-ISA baseline smoke test, PASS
amd64/aarch64/riscv64 all boot clean to [zuse@Hera] ok>, zero UNKNOWN
WORD, and dict_hash identical across all three: Hera (PARITY:M7.1a)
0x6824fe5993239838, Hermes 0x062252c4da6858da, Artemis
0xed80117724c26f36. This is the gate task 0.0 exists for -- every later
task's acceptance in this document assumes this baseline is known-good.

Found and worked around a real confound along the way: disk/artemis.img
is deliberately shared across all three ISAs' qemu targets (FABRIC-3.md
SXXXV.2), so a same-order rerun has run 2 and 3 silently resume run 1's
already-formatted disk instead of formatting their own. The first
attempt (logs/20260919-124835 amd64, logs/20260919-124952 aarch64,
both kept for the record) shows exactly this: Artemis's dict_hash
diverges between the two runs even though Hera's and Hermes's do not,
because only Artemis's birth path branches on disk state. Restored
disk/artemis.img and disk/thumbdrives/zuse-thumb-ident.img to their
committed blank state before each of the three reruns that produced
the clean, matching baseline above, and recorded the finding in
FABRIC-3.6.md so a later task doesn't mistake the same confound for a
real architecture divergence.

capsules/BLOCK_MAP.md's timestamp header is regenerated by the build,
per .claude/CLAUDE.md's documented behavior for that generated file.

Authorized by Captain Bob ("begin", "clean new disk",
"document, commit, and push").

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 13:08:32 -04:00
948 changed files with 1490876 additions and 3525 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ Checks: >
WarningsAsErrors: [ ]
HeaderFilterRegex: '^src/.*|^include/.*'
HeaderFilterRegex: '^v3/(src|include)/.*|^kernel/(src|include)/.*'
AnalyzeTemporaryDtors: false
FormatStyle: none
+28 -28
View File
@@ -6,8 +6,8 @@
# clang-tidy will ALWAYS false-positive them. Always.
# ---------------------------------------------------------
src/platform/**
src/platform/*
v3/src/platform/**
v3/src/platform/*
# ---------------------------------------------------------
# Skip test infrastructure
@@ -15,8 +15,8 @@ src/platform/*
# and stress edge conditions clang-tidy will never grok.
# ---------------------------------------------------------
src/test_runner/**
src/test_runner/*
v3/src/test_runner/**
v3/src/test_runner/*
# ---------------------------------------------------------
# Skip word definitions generated or "mechanically patterned"
@@ -24,30 +24,30 @@ src/test_runner/*
# clang-tidy gives ZERO useful feedback on them.
# ---------------------------------------------------------
src/word_source/string_words.c
src/word_source/block_words.c
src/word_source/arithmetic_words.c
src/word_source/mixed_arithmetic_words.c
src/word_source/return_stack_words.c
src/word_source/defining_words.c
src/word_source/stack_words.c
src/word_source/logical_words.c
src/word_source/memory_words.c
src/word_source/io_words.c
src/word_source/dictionary_words.c
src/word_source/dictionary_manipulation_words.c
src/word_source/vocabulary_words.c
src/word_source/system_words.c
src/word_source/starforth_words.c
src/word_source/editor_words.c
src/word_source/format_words.c
v3/src/word_source/string_words.c
v3/src/word_source/block_words.c
v3/src/word_source/arithmetic_words.c
v3/src/word_source/mixed_arithmetic_words.c
v3/src/word_source/return_stack_words.c
v3/src/word_source/defining_words.c
v3/src/word_source/stack_words.c
v3/src/word_source/logical_words.c
v3/src/word_source/memory_words.c
v3/src/word_source/io_words.c
v3/src/word_source/dictionary_words.c
v3/src/word_source/dictionary_manipulation_words.c
v3/src/word_source/vocabulary_words.c
v3/src/word_source/system_words.c
v3/src/word_source/starforth_words.c
v3/src/word_source/editor_words.c
v3/src/word_source/format_words.c
# ---------------------------------------------------------
# Skip headers belonging to the word system
# Same reason: no signal, infinite false positives.
# ---------------------------------------------------------
src/word_source/include/*
v3/src/word_source/include/*
# ---------------------------------------------------------
# Skip anything that implements performance knobs,
@@ -55,15 +55,15 @@ src/word_source/include/*
# These are meant to be weird, and tidy can't reason about them.
# ---------------------------------------------------------
src/stack_management.c
src/profiler.c
src/vm_debug.c
src/physics_runtime.c
v3/src/stack_management.c
v3/src/profiler.c
v3/src/vm_debug.c
v3/src/physics_runtime.c
# ---------------------------------------------------------
# Skip integration + stress torture-tests.
# These *intentionally* do UB-like bullshit.
# ---------------------------------------------------------
src/test_runner/modules/*stress*
src/test_runner/modules/*integration*
v3/src/test_runner/modules/*stress*
v3/src/test_runner/modules/*integration*
+54 -38
View File
@@ -11,20 +11,31 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
> **Superseded subsystem docs (Captain Bob, 2026-08-15):** `.claude/TRIPOD.md`,
> `.claude/HERMES.md`, `.claude/ARTEMIS.md`, and `.claude/CONSOLE.md` are all superseded —
> `FABRIC-0.md`, `FABRIC-1.md`, and `FABRIC-2.md` (all design history/archival as of
> 2026-09-04) and `FABRIC-3.md` (current/living, topic: bare metal boot, read this one first)
> are the sole authoritative source for Tripod/Hermes/Artemis/Console work now. The four
> subsystem docs remain in the repo as historical record only; each carries its own
> superseded-header pointing here. Do not treat them as current, do not read them for design
> authority, and do not cite them in place of `FABRIC-0.md`/`FABRIC-1.md`/`FABRIC-2.md`/
> `FABRIC-3.md`.
> 2026-09-04), `FABRIC-3.md` (current/living, topic: bare metal boot), and `FABRIC-3.5.md`/
> `FABRIC-3.6.md` (Tripod/kernel reshuffle, design + execution) are the sole authoritative
> source for Tripod/Hermes/Artemis/Console work now. The four subsystem docs remain in the
> repo as pure historical record — they describe the pre-reshuffle Hera/Hermes/Artemis fleet,
> and `.claude/HERMES.md` in particular now describes a VM that no longer exists in this
> codebase at all, not merely a superseded design. Each carries its own superseded-header
> pointing here. Do not treat them as current, do not read them for design authority, and do
> not cite them in place of `FABRIC-0.md`/`FABRIC-1.md`/`FABRIC-2.md`/`FABRIC-3.md`/
> `FABRIC-3.5.md`/`FABRIC-3.6.md`. **Decision (Captain Bob, 2026-09-22, FABRIC-3.6.md task
> 5.2): archive as-is, do not rewrite.** These four docs stay frozen at their 2026-08-15
> superseded state; they are not updated to reflect Hermes's removal, since they are already
> explicitly out of scope for current design authority and rewriting historical record to
> track a codebase it no longer describes would defeat its purpose as a record.
> **WORK IN PROGRESS — Tripod/kernel reshuffle (2026-09-19).** Hermes moves into the kernel;
> the Tripod becomes **Hera / Artemis / Hestia**. Design is complete and ruled in
> **`FABRIC-3.5.md`** (authoritative); execution is tracked in **`FABRIC-3.6.md`** — **start
> there, at its `START HERE` section.** `FABRIC-3.md` remains open and authoritative for its
> own topic (bare metal boot); nothing in the reshuffle supersedes it. **No reshuffle code has
> been written yet.** The descriptions of the Tripod elsewhere in this file describe the
> *current* fleet (Hera/Hermes/Artemis) and stay correct until the reshuffle lands.
> **Tripod/kernel reshuffle — Phases 0–4 COMPLETE (2026-09-22); Phase 5 close-out in
> progress.** Hermes moved into the kernel as kernel-Hermes (`src/starkernel/vm/kernel_
> hermes.c`); the Tripod is now **Hera / Artemis / Hestia** — **the Hermes VM, `capsules/
> hermes/init.4th`, and `capsules/common/messaging.4th` no longer exist in this codebase.**
> Design is complete and ruled in **`FABRIC-3.5.md`** (authoritative); execution is tracked in
> **`FABRIC-3.6.md`**. `FABRIC-3.md` remains open and authoritative for its own topic (bare
> metal boot); nothing in the reshuffle supersedes it. **The Tripod/fleet descriptions
> elsewhere in this file have been updated to match** (Hera/Artemis/Hestia, no Hermes) — this
> is no longer a "stays correct until it lands" caveat, it already landed. Remaining:
> Isabelle pass ✅, doc sweep (this note) ✅, SBOM/version bump/master-merge still open — see
> `FABRIC-3.6.md` Phase 5 for status.
> **Scope:** This repo is LithosAnanke — the bare-metal UEFI kernel that boots StarForth
> directly on hardware. StarForth (the hosted FORTH-79 VM) has its own separate repository
@@ -181,8 +192,11 @@ userspace runtime. No libc, no traditional OS underneath. It is the bare-metal t
removed as an active target.)
**Current status: M7.1** — capsule birth protocol, Mama FORTH vocabulary, Tripod multi-VM
fleet (Hera/Hermes/Artemis), and word-level ACL (Phases 1–7) are live; POST at boot verifies
parity hash across amd64/aarch64/riscv64 with a 453-word Mama capsule dictionary.
fleet (Hera/Artemis/Hestia — Hermes moved into the kernel as kernel-Hermes, FABRIC-3.6.md
Phase 4, 2026-09-22), and word-level ACL (Phases 1–7) are live; POST at boot verifies parity
hash across amd64/aarch64/riscv64 with a 530-word Mama capsule dictionary (word count as of
the Phase 4 strip; verify against a live `PARITY:M7.1a word_count=` line before citing, it
changes with any new C word registration).
**Key distinguishing features:**
@@ -191,9 +205,11 @@ parity hash across amd64/aarch64/riscv64 with a 453-word Mama capsule dictionary
- Content-addressed, immutable **capsules** as the primary organizational unit — no dynamic
allocator in the traditional sense; identity is a content hash (XXHash64), mutation
produces a new capsule
- **Tripod** — a named multi-VM fleet (Hera the Mama VM, two Hermes instances, Artemis)
that births, runs, and re-births independently; verified booting live pre-REPL on all
three architectures. See `.claude/TRIPOD.md`, `.claude/HERMES.md`, `.claude/ARTEMIS.md`.
- **Tripod** — a named multi-VM fleet (Hera the Mama VM, Artemis, Hestia) that births, runs,
and re-births independently; verified booting live pre-REPL on all three architectures.
Hermes moved into the kernel as kernel-Hermes (`src/starkernel/vm/kernel_hermes.c`) and is
no longer a Tripod VM — see `FABRIC-3.5.md`/`FABRIC-3.6.md`, not the archived
`.claude/TRIPOD.md`/`.claude/HERMES.md`/`.claude/ARTEMIS.md`.
- Word-level ACL security system with kernel parity (see above) — measured overhead three
orders of magnitude below the measurement floor
- Kconfig-based build configuration (~40 discoverable symbols spanning physics/heartbeat/
@@ -208,43 +224,43 @@ parity hash across amd64/aarch64/riscv64 with a 453-word Mama capsule dictionary
```bash
# Build kernel for a given architecture (amd64 default)
make -f Makefile.starkernel ARCH=amd64
make -f Makefile.starkernel ARCH=aarch64
make -f Makefile.starkernel ARCH=riscv64
make -f kernel/Makefile ARCH=amd64
make -f kernel/Makefile ARCH=aarch64
make -f kernel/Makefile ARCH=riscv64
# Run in QEMU with OVMF
make -f Makefile.starkernel qemu
make -f Makefile.starkernel ARCH=aarch64 qemu
make -f Makefile.starkernel ARCH=riscv64 qemu
make -f kernel/Makefile qemu
make -f kernel/Makefile ARCH=aarch64 qemu
make -f kernel/Makefile ARCH=riscv64 qemu
# Clean
make -f Makefile.starkernel clean
make -f kernel/Makefile clean
```
Output: `build/<arch>/kernel/starkernel_loader.efi` + `build/<arch>/kernel/starkernel_kernel.elf`.
Two independently tracked version strings flow into the generated `include/version.h`:
`VERSION` (`Makefile.starkernel` — the embedded StarForth engine version, currently `3.1.0`;
`VERSION` (`kernel/Makefile` — the embedded StarForth engine version, currently `3.1.0`;
note this does **not** auto-sync with the standalone StarForth repo's own version) and
`LITHOS_VERSION` (`Makefile.starkernel` — the kernel version, currently **`2.0.0`**;
`LITHOS_VERSION` (`kernel/Makefile` — the kernel version, currently **`2.0.0`**;
corrected 2026-09-19 — this file said `2.0.1`, but `FABRIC-3.md` §I.2 rolled it back to
`2.0.0` on 2026-09-04 because `2.0.1` names the SER5 hardware-track line and claims hardware
progress not yet verified. The versioning policy is **semantic, not sequential** — see the
roadmap table in `Makefile.starkernel` before choosing any version).
roadmap table in `kernel/Makefile` before choosing any version).
### Build configuration (Kconfig — real, wired, not vestigial)
Every kernel-only knob (`STARFORTH_ENABLE_VM`, `PARITY_MODE`, the shared physics/heartbeat
family, etc.) is an optional Kconfig symbol defined across `Kconfig`, `Kconfig.arch`,
`Kconfig.heartbeat`, `Kconfig.kernel`, `Kconfig.physics`, `Kconfig.variant` (~40 symbols
total). `Makefile.starkernel` pulls its defaults from this system via a `kconfig_bool(...)`
total). `kernel/Makefile` pulls its defaults from this system via a `kconfig_bool(...)`
mechanism — e.g. `STARFORTH_ENABLE_VM` defaults to **1** (confirmed at
`Makefile.starkernel:61`), meaning a plain `make -f Makefile.starkernel` already builds with
`kernel/Makefile:61`), meaning a plain `make -f kernel/Makefile` already builds with
VM + capsule-birth + ACL active. A bare invocation uses the defaults it always has:
```bash
make -f Makefile.starkernel ARCH=amd64 menuconfig
make -f Makefile.starkernel ARCH=amd64 kernel_amd64_defconfig
make -f kernel/Makefile ARCH=amd64 menuconfig
make -f kernel/Makefile ARCH=amd64 kernel_amd64_defconfig
```
### Hosted VM (vendored, for local sanity only)
@@ -261,7 +277,7 @@ StarForth repo) were removed 2026-08-15 — they referenced
in the actual generated `include/version.h` (which only has `STARFORTH_VERSION`,
`STARFORTH_ARCH`, `STARFORTH_TARGET`, `STARFORTH_TIMESTAMP`, `STARFORTH_VERSION_FULL`,
`LITHOS_VERSION`, `LITHOS_VERSION_STR`), so they could never have worked. Bump versions by hand-editing the `VERSION`/
`LITHOS_VERSION` variables in `Makefile.starkernel` instead. Report the broken targets if
`LITHOS_VERSION` variables in `kernel/Makefile` instead. Report the broken targets if
asked, don't silently fix them.
### Important: Linker Configuration
@@ -282,9 +298,9 @@ The vendored hosted `make` build (above) is NEVER used to validate kernel change
```bash
# Run in this exact order for every kernel change:
make -f Makefile.starkernel ARCH=amd64 clean qemu
make -f Makefile.starkernel ARCH=aarch64 clean qemu
make -f Makefile.starkernel ARCH=riscv64 clean qemu
make -f kernel/Makefile ARCH=amd64 clean qemu
make -f kernel/Makefile ARCH=aarch64 clean qemu
make -f kernel/Makefile ARCH=riscv64 clean qemu
```
**QEMU rule — non-negotiable:** Only ONE QEMU instance may run at a time, always in the
@@ -459,7 +475,7 @@ listed in `proof/ROOT` (corrected 2026-09-19; this file said 23, and `proof/COVE
said 52 all along). **Read `proof/COVERAGE.md` first**: its stated goal is not a green build but
"identify precisely what cannot be proven and why — the boundary between 'formally verified'
and 'not, for this specific reason'." Run `isabelle build -D proof/`
directly; neither `Makefile` nor `Makefile.starkernel` in this repo defines an
directly; neither `Makefile` nor `kernel/Makefile` in this repo defines an
`isabelle-build`/`isabelle-check` target (unlike the standalone StarForth repo, which has a
broken one — this repo simply doesn't have the target at all, so there's nothing to
mistakenly invoke).
@@ -479,7 +495,7 @@ status for kernel work.
- **Strict ANSI C99** — No GNU extensions, no C++ features
- **Zero warnings target, with four explicit exceptions — corrected 2026-08-18, previous
claim was wrong.** Build with `-Wall -Wextra -Werror`, but `Makefile.starkernel` carries
claim was wrong.** Build with `-Wall -Wextra -Werror`, but `kernel/Makefile` carries
`-Wno-error=unused-parameter -Wno-error=shift-negative-value -Wno-error=sign-compare
-Wno-error=missing-field-initializers` — those four classes are enabled (still visible as
warnings) but deliberately downgraded from fatal, everything else is. The previous version
+10 -7
View File
@@ -933,10 +933,12 @@ WARN_LOGFILE = docs/api/doxygen_warnings.log
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched.
INPUT = include \
src \
src/word_source \
src/test_runner \
INPUT = v3/include \
v3/src \
v3/src/word_source \
v3/src/test_runner \
kernel/include \
kernel/src \
README.md
# This tag can be used to specify the character encoding of the source files
@@ -2366,9 +2368,10 @@ SEARCH_INCLUDES = YES
# RECURSIVE has no effect here.
# This tag requires that the tag SEARCH_INCLUDES is set to YES.
INCLUDE_PATH = include \
src/word_source/include \
src/test_runner/include
INCLUDE_PATH = v3/include \
kernel/include \
v3/src/word_source/include \
v3/src/test_runner/include
# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
# patterns (like *.h and *.hpp) to filter out the header-files in the
-282
View File
@@ -1,282 +0,0 @@
# FABRIC-3.6.md — the Tripod/kernel reshuffle: execution log
> ## START HERE — session handoff
>
> **If you have just been told "go build it", read this section, then `FABRIC-3.5.md`'s §XLI,
> then start at task 0.0 below. Do not re-derive the design — it is settled.**
>
> **Where the code is.** This repo is **LithosAnanke, on the Gitea instance at
> `gitea.strshipos.com`, repo `admin/LithosAnanake`** — Captain Bob has the credentials.
> **Confirm you are in the right repo before anything else.** `git remote -v` must show
> `gitea.strshipos.com/admin/LithosAnanake`. **Nothing outside Gitea is this project.** (The
> handoff was authored in a container whose default working directory was a *different*, empty
> repo on an unrelated remote — if you ever see that, you are in the wrong place.) `master` is
> the sole production line. Work on branch **`claude/starshipos-tripod-kernel-reshuffle-itbjns`**
> (a clean fast-forward of `master` at `e56974e`; **documentation only — no reshuffle code has
> been written**. The branch head is the source of truth; do not trust a commit id quoted here).
>
> **The two documents.** `FABRIC-3.5.md` is the **design record and is authoritative** — every
> ruling, with its reasoning and evidence, §I–§XLIII. **This** document is the execution log.
> Rulings are cited here, never restated. **If a task proves a ruling wrong, record the finding
> here and amend `FABRIC-3.5.md` — never silently diverge.**
>
> **First action: task 0.0**, the three-ISA baseline. It is a gate, not a formality — see its
> own rationale below. **Nothing else starts until it is recorded.**
>
> **Five traps this project's own history proves are real. A fresh session will walk into these:**
>
> 1. **Silent failure is the dominant mode here.** `FABRIC-3.5.md` §XXXV.0 lists four
> independent instances. Most relevantly: *"a switch storm and a healthy idle REPL produce an
> identical serial log."* **A green boot is weak evidence.** Name the specific observation
> that proves a task worked, before running it.
> 2. **`grep` cannot establish capsule reachability.** Capsules are birthed by name from runtime
> strings. A reference count over `capsules/` and `src/` put the six live `init-l8-*`
> capsules at **zero references**; including `experiments/` found 18–19 each. §XXII.2. **Use
> the three routes, never grep alone.**
> 3. **`fleet_conserved` cannot see Stadium heat.** The two heat accountings are entirely
> decoupled (§XXXIX). A leaking message allocator leaves `fleet_conserved` reporting a serene
> `1`. **Stage B's evidence is the ledger plus `stadium_conserved()`, never `fleet_conserved`.**
> 4. **`dict_hash` *changing* is expected; `dict_hash` *diverging* across architectures is the
> stop condition.** §XXXIV.6. Do not "fix" a changed hash.
> 5. **Documentation in this repo drifts from the code — trust the code.** `.claude/CLAUDE.md`
> carried four stale claims; **all four were corrected 2026-09-19 (§XLIV) and it is now
> reliable.** Others are not: `capsules/MANIFEST.md` still describes block 4055 as a live
> "immutable ABI" that `FABRIC-2.md:2773` declared stale, and still misdescribes block 2049.
> `FABRIC-0.md` §25.7 lists resolved defects as open. **Where a document and the code
> disagree, the code wins — and record the drift rather than working around it.**
>
> **Blocked, and not to be worked around:** Phase 3 needs three decisions from Captain Bob —
> **B1** channels (one membership or negotiation), **B2** the `SK_SWITCH_MAX_SLOTS` ceiling of
> 16, **B4** the payload bound against `INPUT_BUFFER_SIZE` 1025. **All design is complete; these
> are rulings, not investigations.**
>
> **Do not:** create branches, stash, fix defects found in passing, bundle tasks into one
> commit, or start any task without explicit authorization. Captain Bob's Law,
> `.claude/CLAUDE.md`.
**Status: LIVING WORKING DOCUMENT, opened 2026-09-19.** This is the **execution** record for
the reshuffle designed in `FABRIC-3.5.md`. Work is tracked, annotated and closed here.
**Why this is a separate document, per the series' own rule.** `FABRIC-1.md` closed at **4,420
lines** for a stated reason: *"continuing to append here made the still-open work hard to find."*
`FABRIC-3.5.md` stands at **4,452 lines** with 40+ open punch items scattered among hundreds of
settled rulings — **it has crossed the same threshold, for the same reason.** Annotating a task
list inside it, commit by commit, would bury the design record it exists to be.
**The split of responsibility is strict, and stated because `FABRIC-3.5.md` §XXXI found that
documents in this series lose track of each other:**
| | `FABRIC-3.5.md` | `FABRIC-3.6.md` (this) |
|---|---|---|
| **Holds** | The design record — every ruling, its reasoning, its evidence | The work — tasks, results, dates, commits |
| **State** | Design phase closed; archival close at `v2.1.0` (§XXVI.5) | Living until the work is done |
| **On a conflict** | **Authoritative** | Defers, and records the discrepancy |
**Design rulings are never restated here, only cited** (`§XXXIV.2`, `§XL.4`, …). If a task needs
a rule explained, read `FABRIC-3.5.md`. **If executing a task proves a ruling wrong, that is a
finding: record it here and amend `FABRIC-3.5.md` there** — never silently diverge.
**The checkbox convention is deliberate.** `FABRIC-3.5.md` §XXXI.2 found the series'
carry-forward discipline was mechanically auditable (`grep -c '\- \[ \]'`) up to `FABRIC-2.md`,
and **broke at `FABRIC-3.md`, which uses no checkboxes at all** — after which "is anything still
open?" stopped being a `grep` and became a reading exercise. **This document restores the
convention**, so that question stays answerable by machine.
---
## Standing rules
Apply to **every** task, from `.claude/CLAUDE.md` and `FABRIC-3.5.md` §XXXIV:
- **One task, one commit.** No bundling.
- **Acceptance is the three-architecture QEMU boot** — `clean qemu`, amd64/aarch64/riscv64, one
at a time, in the foreground. Logs committed. There is no other acceptance test.
- **`dict_hash` changing is expected. `dict_hash` *diverging* between architectures is a stop
condition** (§XXXIV.6).
- **`mkcapsule --lint capsules/` clean** after any capsule change.
- **No task starts without explicit authorization.** Captain Bob's Law.
- **Report, don't fix.** A defect found while doing a task is recorded here, not repaired in
passing.
## Annotation convention
```
- [x] 0.2 — Strip common/msg.4th
2026-09-DD · commit abc1234 · 3-arch boot clean, lint 0 violations
note: <anything surprising; a finding gets its own entry below>
```
Mark `[~]` for started-not-finished, with what is outstanding. **Never mark `[x]` on a task
whose check did not actually run** — `FABRIC-3.5.md` §XXXV.6 records that declaring done too
early is this project's most repeated failure.
---
## Pre-flight — establish the baseline before anything changes
**Branch state at open (2026-09-19):** `claude/starshipos-tripod-kernel-reshuffle-itbjns`, head
`3a4e5cf`, **working tree clean, pushed, 33 commits ahead of `master`** — all documentation, no
code. `master` is unmoved at `e56974e`, so the branch remains a clean fast-forward. **Execution
starts from this commit.**
- [ ] **0.0** — **Three-ISA baseline smoke test on the unmodified branch.**
`make -f Makefile.starkernel ARCH=<arch> clean qemu` for amd64, aarch64, riscv64 — one at
a time, in the foreground, per `.claude/CLAUDE.md`.
*Check:* all three reach `zuse)ok>`; zero `UNKNOWN WORD`; logs committed under
`logs/<timestamp>/<arch>/`; **record each `dict_hash` and confirm the three are
identical.**
**Why this is a gate and not a formality.** Every task in this document takes the
three-architecture boot as its acceptance (§XXXIV). **Without a known-good baseline captured
first, the first red boot is ambiguous** — pre-existing fault or something task 0.2 just did?
This project has been bitten by exactly that ambiguity before (`FABRIC-3.md` §XXVI: "the
lockdown was never broken — wrong VM tested"). The baseline is what makes every later
acceptance run interpretable, and the recorded `dict_hash` triple is the reference every
subsequent §XXXIV.6 divergence check compares against.
**Run it yourself — you are expected to.** Captain Bob confirmed (2026-09-19) that execution
happens on his PC with the full toolchain installed, so **the session reading this can and
should run task 0.0 directly**, once authorized. Verify first that `qemu-system-x86_64`,
`qemu-system-aarch64`, `qemu-system-riscv64`, the aarch64/riscv64 cross-compilers and
OVMF/AAVMF are all present; **if any are missing you are not on the intended machine — stop and
say so rather than improvising a partial test.**
*(Historical note: this document was authored in a container with none of that toolchain, which
is why task 0.0 was written but never run. Do not take its unchecked state as a prior failure —
it has simply not been attempted.)*
---
## Phase 0 — Preparation (no behaviour change)
**Gate:** all three architectures boot to `zuse)ok>`, `stadium_conserved()` true, no
`UNKNOWN WORD`.
- [ ] **0.1** — Establish Category A reachability by §XXII.2's three routes (boot path, tooling,
baked capsule directory). **Never by grep alone.**
*Check:* a written list naming the route that proves each entry dead.
- [ ] **0.2** — Strip `capsules/common/msg.4th`. *Check:* 3-arch boot; lint clean.
- [ ] **0.3** — Strip `capsules/process.4th` (takes `EVENT-EMIT`/`-WAIT`/`-DRAIN` with it,
§XXXIII.3). *Check:* 3-arch boot; lint clean.
- [ ] **0.4** — Strip `SPAWN-EVENT`. *Check:* 3-arch boot; lint clean.
- [ ] **0.5** — Correct `capsules/MANIFEST.md` blocks 4055 and 2049 **as their files are
stripped** (§XXII.5). *Check:* manifest describes no file that no longer exists.
- [ ] **0.6** — Return freed block ranges to `capsule-reserved.txt`. *Check:* lint clean.
- [ ] **0.7** — Add `stadium_conserved()`: `Σ patron + reservoir + consumed == Q48_ONE`,
**epsilon zero** (§XL.4, item 41). *Check:* true on a clean boot, all three arches.
- [ ] **0.8** — Audit that `PLOT`/`FB-WIDTH`/`FB-HEIGHT` are registered **nowhere** but the
table Hestia will own (item 33). *Check:* read-only; a second site is a defect to report.
## Phase 1 — Hestia (messaging untouched)
**Gate:** Tripod is Hera/Artemis/Hestia **plus** Hermes; drawing works from Hestia and only
Hestia; headless policy intact.
- [ ] **1.1** — Allocate Hestia's block range vs. `capsule-reserved.txt`, **avoiding 4997**.
*Check:* lint clean.
- [ ] **1.2** — Create `capsules/hestia/init.4th`: messaging load, `MSG-CD-INIT`, banner. **No
fabric yet.** *Check:* boots; Hestia not yet birthed.
- [ ] **1.3** — Add Hestia to `is_fleet_foundation` (`capsule_birth.c:793-796`) — **four names,
Hermes retained** (§XXXIV.4). *Check:* 3-arch boot; Hermes still live.
- [ ] **1.4** — Birth Hestia in `kernel_main.c`, **alongside** Hermes's existing birth.
*Check:* registry shows both; `dict_hash` identical across arches.
- [ ] **1.5** — Register Hestia for switch signals (the `:1007` pattern). *Check:* boot clean;
**no switch storm** (§XXVIII.2's shape — and note it is invisible by default, §XXXV.0).
- [ ] **1.6** — Move `fabric.4th` from `init.4th` to `hestia/init.4th`. *Check:* Hera's dict
shrinks, Hestia's grows; cross-arch identity holds.
- [ ] **1.7** — Move `font.4th` likewise. *Check:* same.
- [ ] **1.8** — Move `PLOT`/`FB-WIDTH`/`FB-HEIGHT` registration to Hestia's table **only**.
*Check:* **positively verify** a non-Hestia VM calling `PLOT` gets `UNKNOWN WORD`.
- [ ] **1.9** — Assert §XVIII.6's headless invariant: Hestia's birth sets no
`g_wirebind_attached_username` and mints no proxy. *Check:* boot headless, no thumbdrive,
**no prompt appears**.
## Phase 2 — Allocator and audit (inert) — **the real go/no-go**
**Gate:** task 2.7. **If it fails, stop and re-plan. Do not proceed to Phase 3.**
- [ ] **2.1** — Kernel-Hermes message/membership structures, **wired to nothing, drawing no
heat**. *Check:* boot byte-identical; `dict_hash` unmoved.
- [ ] **2.2** — Allocate: pull `Q.SLOT` from the caller's reservoir; roll back on refusal.
*Check:* N allocs against a known reservoir; refusal at the right count.
- [ ] **2.3** — Release: return remaining heat via the eviction path. *Check:* reservoir
restored exactly for an undecayed message.
- [ ] **2.4** — The four counters: `held`, `pulled`, `returned`, `consumed` (§XL.4). *Check:*
each increments at exactly one site.
- [ ] **2.5** — Decay: apply, and **record the delta into `consumed`** (§XXXVII.3). *Check:*
`consumed` grows by exactly `heat_before − heat_after`.
- [ ] **2.6** — Self-audit: `held == pulled − returned − consumed`, **epsilon zero**. *Check:*
holds across the cycle; **deliberately corrupt a counter → audit fires on the first unit**.
- [ ] **2.7** — **Stage B proof** (§XXXIV.3 as corrected by §XXXIX.4): alloc/free cycle
verifying **(a)** the ledger and **(b)** `stadium_conserved()` before and after.
*Check:* both true, all three arches. **`fleet_conserved` is not evidence here** — it
cannot see Stadium heat (§XXXIX.1).
- [ ] **2.8** — Scan-based cross-check of the counters, diagnostics only, off the hot path
(§XXXVII.4). *Check:* scan agrees with counters.
## Phase 3 — Cutover — **BLOCKED**
**Not buildable until all three clear.** Shape once unblocked (§XXXIV.3): cut over
`BLK-ATTACH-EVENT` alone, then remaining types one at a time, under §XXXIV.2's partition rule —
one message type owned by exactly one layer, no message shared.
- [ ] **B1** — **Item 27**: channels — negotiation, or one broadcast membership? (§XXXIII.5
recommends the latter; unruled.)
- [ ] **B2** — **Item 32**: `SK_SWITCH_MAX_SLOTS` is 16 and §XXXII made the switcher the sole
mover of control. Constant bump or table redesign?
- [x] **B3** — **CLEARED 2026-09-19 by `FABRIC-3.5.md` §XLIII**: the target drains its own
queue at its own outermost interpret checkpoint; kernel-Hermes publishes and never
dispatches. Reuses `sk_vm_at_outermost_interpret()` and the Stage 3 checkpoint.
- [ ] **B4** — **Item 44**: payload bound or chunking against `INPUT_BUFFER_SIZE` 1025
(§XLIII.6.1). Decide before Phase 3.
## Phase 4 — Category B strip
Only after every live type is cut over. **Hermes leaves `is_fleet_foundation` and
`kernel_main.c` here, not earlier** (§XXXIV.4).
- [ ] **4.1** — Strip `capsules/hermes/init.4th`.
- [ ] **4.2** — Strip the FORTH routing table and slot-3 pairing convention.
- [ ] **4.3** — Strip `capsules/common/messaging.4th`.
- [ ] **4.4** — Remove Hermes from `is_fleet_foundation` and its birth from `kernel_main.c`.
## Phase 5 — Close-out
- [ ] **5.1** — Isabelle/HOL pass. **Deliverable is the restated boundary**, explicitly
including §XXV.4's coverage loss — *not* a green build (§XXV.3).
- [~] **5.2** — Documentation sweep, grepping **by exclusion** (§XXVIII.3).
**CLAUDE.md's four errors: DONE 2026-09-19 (§XLIV), pointer to this document added.**
Outstanding: `MANIFEST.md` (rides the strip, §XXII.5), the `TRIPOD.md`/0.1 contradiction,
item 42's K-qualification, the superseded subsystem docs' update-or-archive call, and
item 45 (`experiments/bare_metal/README.md` block framing).
- [ ] **5.3** — `make sbom`; check `Created:` and `DocumentName` (§XXVI.2).
- [ ] **5.4** — `LITHOS_VERSION = 2.1.0`; engine `VERSION` per §XXX.6's rule; roadmap table
gains its `2.1.0` line in two live places (§XXVIII.2).
- [ ] **5.5** — Resolve the stray `refs/heads/v2.0.1` and PR #1 (§XXVI.4). **Investigate, do
not delete.**
- [ ] **5.6** — Merge to `master`; tag `v2.1.0`.
- [ ] **5.7** — Archival close of `FABRIC-3.5.md` (§XXVI.5) and of this document.
---
## Findings log
*Defects and surprises found while executing. Reported, not fixed (unless the task was to fix
them). A finding that contradicts a `FABRIC-3.5.md` ruling must also be amended there.*
*(none yet — work not started)*
---
## Out of scope, carried for visibility
Recorded in `FABRIC-3.5.md`, deliberately **not** part of this reshuffle. Listed so they are not
absorbed by accident:
- **Item 43** — should a Stadium reservoir ever replenish? (§XL.6) A VM's send capacity declines
monotonically today.
- **Items 23–26** — the `FABRIC-3.5.md` §XXXI gap-analysis follow-ups: re-home `FABRIC-2` §17.4,
consolidate the three reported-not-scheduled registries, decide `FABRIC-2`'s five orphaned
design items, reconcile `FABRIC-0`'s seven opens.
- **`src/*.c.bak`** — tracked-but-stale, reported in three places and actioned in none
(§XXII.5).
+1 -1
View File
@@ -5,7 +5,7 @@ choice
default ARCH_AMD64
help
Selects the value passed as ARCH= to whichever Makefile
(Makefile or Makefile.starkernel) is driven from this config.
(Makefile or kernel/Makefile) is driven from this config.
Both Makefiles already accept "amd64"/"aarch64"/"riscv64" as
canonical spellings (each has its own alias-normalizing logic
for other spellings like x86_64/arm64/riscv), so this choice
+1 -1
View File
@@ -17,7 +17,7 @@ config HEARTBEAT_THREAD_ENABLED
builds without needing a special case. This is UI/model
correctness (menuconfig can't offer a choice LithosAnanke has no
way to honor -- there are no pthreads in a freestanding kernel),
not the sole enforcement mechanism: Makefile.starkernel additionally
not the sole enforcement mechanism: kernel/Makefile additionally
keeps its own unconditional `VM_FEATURE_OVERRIDES +=
-DHEARTBEAT_THREAD_ENABLED=0` post-override exactly as it was
before this migration. Deliberately redundant with the `depends
+1 -1
View File
@@ -11,7 +11,7 @@ config STARFORTH_ENABLE_VM
kernel that only reaches the M0-M6 hardware milestones (console,
PMM, VMM, interrupts, timers, kmalloc) with no FORTH interpreter,
no capsules, no "ok" REPL. Gates a large source-file selection
block in Makefile.starkernel, not just a handful of -D flags.
block in kernel/Makefile, not just a handful of -D flags.
config PARITY_MODE
bool "Deterministic parity harness mode (PARITY_MODE)"
+133 -1314
View File
File diff suppressed because it is too large Load Diff
+46 -7
View File
@@ -6,6 +6,25 @@
---
## Products
StarForth v4 moves the engine onto a 32-instruction core derived from Chuck Moore's F18
(`docs/v4.0.0/`). With it, this repository carries three products, not one:
| Product | What it is |
|---|---|
| **Hosted StarForth F18** | The v4 engine as a native Linux build, for amd64, arm64 and riscv64, on real hardware. |
| **FPGA StarForth F18** | A 32-bit build of the same engine loaded into the FPGA. It is the gateway to the rest: the foundation everything else is built up from. |
| **StarshipOS** | The bare-metal product: LithosAnanke running the v4 F18 engine. |
FORTH-79 is recomposed on the F18 engine and stored as a capsule; the StarshipOS-specific
vocabulary gets the same treatment. The source tree will be reorganised around this split.
v3 remains the reference system until v4 meets its acceptance criteria
(`docs/v4.0.0/JUSTIFICATION.md` §16). Everything below describes v3 as it stands today.
---
## Status — M7.1 (Capsule System · Multi-VM Fleet)
| Milestone | | Status |
@@ -48,19 +67,39 @@ POST at boot: **parity hash verified across amd64/aarch64/riscv64 · Mama capsul
## Quick Start
```bash
# One boot image per physical target (output: build/boards/<board>/)
make boot_image TARGET=SER5 # Beelink SER5 -- GPT/ESP, BOOTX64.EFI
make boot_image TARGET=RASPI # Raspberry Pi 5 -- FAT, config.txt + kernel_2712.img + DTB
make boot_image TARGET=MILKV # Milk-V Mars -- GPT/ESP, BOOTRISCV64.EFI (U-Boot bootefi)
make boot_image TARGET=ZYNQ7020 # Zynq-7020 -- ARMv7 port in progress
make boards # list boards
# The same TARGET drives all, docs and clean (make help)
make # every board with a kernel port + hosted v3 + the book
make TARGET=RASPI # one board's image + build/docs/LithosAnanke-raspi.pdf
make docs # build/docs/LithosAnanke.pdf (LaTeX master: docs/book/main.tex)
make clean TARGET=RASPI # that board's outputs and build/aarch64/kernel
make clean # all of build/ except build/cache/
# Build kernel (requires cross-compilation toolchain, or native gcc)
make -f Makefile.starkernel ARCH=amd64
make -f kernel/Makefile ARCH=amd64
# Run in QEMU with OVMF
make -f Makefile.starkernel qemu
make -f kernel/Makefile qemu
# Other architectures
make -f Makefile.starkernel ARCH=aarch64 qemu
make -f Makefile.starkernel ARCH=riscv64 qemu
make -f kernel/Makefile ARCH=aarch64 qemu
make -f kernel/Makefile ARCH=riscv64 qemu
```
Artifacts: `build/amd64/kernel/starkernel_loader.efi` · `build/amd64/kernel/starkernel_kernel.elf`
Tree: `kernel/` (LithosAnanke: `Makefile`, `src/`, `include/starkernel/`, `linker/`),
`v3/` (StarForth v3 engine and hosted build: `Makefile`, `src/`, `include/`),
`v4/` (StarForth v4, see `docs/v4.0.0/`), `boards/<board>/` (`board.mk` + boot files).
Every makefile runs from the repo root; the root `Makefile` forwards any other goal to
`v3/Makefile` (hosted build).
For the hosted VM by itself (Linux, no cross-compiler needed, no bare-metal tooling): see
the separate **StarForth** repository — LithosAnanke used to be a branch inside that repo,
now it's its own project with its own `master`.
@@ -69,12 +108,12 @@ now it's its own project with its own `master`.
Every kernel-only knob (`STARFORTH_ENABLE_VM`, `PARITY_MODE`, the shared
physics/heartbeat family, etc.) is an optional Kconfig symbol — a plain
`make -f Makefile.starkernel` uses the same defaults it always has unless
`make -f kernel/Makefile` uses the same defaults it always has unless
you opt in:
```bash
make -f Makefile.starkernel ARCH=amd64 menuconfig
make -f Makefile.starkernel ARCH=amd64 kernel_amd64_defconfig
make -f kernel/Makefile ARCH=amd64 menuconfig
make -f kernel/Makefile ARCH=amd64 kernel_amd64_defconfig
```
---
+29
View File
@@ -0,0 +1,29 @@
# boards/
One directory per physical target. `make boot_image TARGET=<NAME>` (repo root) maps the
name to a directory here, reads its `board.mk`, and builds the single file that goes on
that board's boot medium into `build/boards/<board>/`.
| TARGET | Directory | ISA | Boot path | Image |
|---|---|---|---|---|
| `SER5` | `ser5/` | amd64 | UEFI firmware, removable-media path | GPT + FAT32 ESP: `EFI/BOOT/BOOTX64.EFI` |
| `RASPI` | `raspi/` | aarch64 | Pi 5 EEPROM firmware, native | MBR + FAT32: `config.txt`, `kernel_2712.img`, `bcm2712-rpi-5-b.dtb` |
| `MILKV` | `milkv/` | riscv64 | SPI-flash U-Boot + OpenSBI, `bootefi` | GPT + FAT32 ESP: `EFI/BOOT/BOOTRISCV64.EFI` |
| `ZYNQ7020` | `zynq7020/` | armv7 | BootROM, `BOOT.BIN`, U-Boot | not yet: needs `kernel/src/arch/armv7` |
`board.mk` sets:
- `BOARD_DESC`: one-line description (`make boards`)
- `BOARD_ARCH`: kernel ISA (`kernel/src/arch/<arch>`)
- `BOARD_BOOT`: image recipe in `kernel/Makefile` (`uefi-esp`, `rpi-native`, ...)
- `BOARD_IMAGE`: output file name
Disk images are assembled by `scripts/mkdiskimage.sh`, which needs no root access and no loop
devices. `KERNEL_ARGS="..."` is written as `starforth.cfg` (UEFI boards) or `cmdline.txt`
(Pi 5, where the firmware copies it into `/chosen/bootargs`). `BOOT_IMAGE_SIZE_MIB` sets the
image size; the default is 128.
Hardware status: none of these images has been booted on real silicon from this build. The
SER5 image has been booted in QEMU (q35 + OVMF, from its own disk) through POST. The Pi 5
native path (`raspi/README.md`) and the JH7110 peripherals have still only been checked by
compiling.
+30
View File
@@ -0,0 +1,30 @@
# Milk-V Mars boot media
`make boot_image TARGET=MILKV` builds `build/boards/milkv/lithos-milkv.img`: a GPT disk with one
FAT32 EFI System Partition. Write it to an SD card with `dd`. The partition holds:
- `EFI/BOOT/BOOTRISCV64.EFI`: the riscv64 monolithic loader, the same one QEMU boots.
- `startup.nsh`: for a UEFI shell, if one is used.
- `starforth.cfg`: only present when `KERNEL_ARGS="..."` is given.
The board's own SPI-flash U-Boot, with OpenSBI underneath, finds the loader through its standard
`bootefi` scan of the removable-media path. U-Boot passes its devicetree to the loader through the
EFI configuration table.
Status: booted in QEMU through the same chain the board uses: OpenSBI, then U-Boot
`qemu-riscv64_smode`, then `bootefi` from this image's ESP, then the loader and the kernel. POST
passed, Stadium conservation was exact (resident 43691 + reservoir 21845 = 65536 = `Q48_ONE`), and
Hestia and Artemis came up. Not yet booted on the Mars itself. The JH7110 peripherals
(framebuffer, RNG) have only been checked by compiling.
To repeat the QEMU boot (`qemu-system-riscv64` is in Ubuntu 26.04's `qemu-system-riscv` package;
OpenSBI and U-Boot come from `opensbi` and `u-boot-qemu`). Use a copy, because the kernel writes
to its disk:
```
cp build/boards/milkv/lithos-milkv.img /tmp/mv.img
qemu-system-riscv64 -machine virt -cpu rv64 -m 2048 -nographic \
-bios /usr/lib/riscv64-linux-gnu/opensbi/generic/fw_dynamic.bin \
-kernel /usr/lib/u-boot/qemu-riscv64_smode/u-boot.bin \
-drive if=none,format=raw,file=/tmp/mv.img,id=d0 -device virtio-blk-device,drive=d0
```
+11
View File
@@ -0,0 +1,11 @@
# boards/milkv/board.mk -- Milk-V Mars (StarFive JH7110, SiFive U74, RV64GC).
#
# The board's own SPI-flash U-Boot (OpenSBI underneath) runs the EFI
# application through its standard distro/bootefi scan: the SD card is a GPT
# disk with one FAT32 EFI System Partition holding EFI/BOOT/BOOTRISCV64.EFI,
# the same riscv64 monolithic loader QEMU boots. U-Boot passes its own
# devicetree through the EFI configuration table.
BOARD_DESC := Milk-V Mars (StarFive JH7110, RV64GC, U-Boot bootefi)
BOARD_ARCH := riscv64
BOARD_BOOT := uefi-esp
BOARD_IMAGE := lithos-milkv.img
+20
View File
@@ -0,0 +1,20 @@
# Raspberry Pi 5 native boot media
`make boot_image TARGET=RASPI` builds `build/boards/raspi/lithos-raspi.img`: an MBR disk
with one FAT32 (LBA) partition. Write it to an SD card with `dd`. The partition holds:
- `config.txt`: checked in here (FABRIC-3.md §IV.3 item 6). It pins `kernel_address=0x80000`
because Pi 5 firmware loads `kernel_2712.img` at 0x200000 by default.
- `kernel_2712.img`: the aarch64 kernel objects relinked at 0x80000 by
`kernel/linker/starkernel-native-rpi5.ld`, entered at `rpi5_native_start`
(`kernel/src/arch/aarch64/native_rpi5_entry.S`, which zeroes `.bss` first), and flattened
with `objcopy -O binary`.
- `bcm2712-rpi-5-b.dtb`: the Raspberry Pi firmware's own stock DTB. It is not built by this
project; it is fetched once from the firmware release pinned in `board.mk`
(`RPI_FIRMWARE_TAG`) into `build/cache/`. To use a local copy, pass `RPI5_DTB=/path/to/dtb`.
- `cmdline.txt`: only present when `KERNEL_ARGS="..."` is given. The firmware copies it into
`/chosen/bootargs`, which `rpi5_native_boot()` parses.
Not yet verified on hardware. The native path still assumes everything `kernel_main()`
needs after a UEFI handoff. The firmware enters at EL2 with the MMU off, which is not what
UEFI hands over, and that difference has not been exercised.
+18
View File
@@ -0,0 +1,18 @@
# boards/raspi/board.mk -- Raspberry Pi 5 (BCM2712, Cortex-A76, aarch64).
#
# Native firmware boot, no UEFI (FABRIC-3.md §IV): the Pi 5 bootloader
# lives in EEPROM and reads config.txt, kernel_2712.img and the board DTB
# from the first FAT partition of the SD card. kernel_2712.img is the
# aarch64 kernel relinked at 0x80000 (kernel/linker/starkernel-native-rpi5.ld)
# and flattened to a raw binary.
#
# The DTB is the Raspberry Pi firmware's own stock file, not built here. It
# is fetched once from the pinned firmware release below into build/cache/
# unless RPI5_DTB points at a local copy.
BOARD_DESC := Raspberry Pi 5 (BCM2712, Cortex-A76, native firmware boot)
BOARD_ARCH := aarch64
BOARD_BOOT := rpi-native
BOARD_IMAGE := lithos-raspi.img
RPI_FIRMWARE_TAG ?= 1.20260915
RPI5_DTB_URL ?= https://raw.githubusercontent.com/raspberrypi/firmware/$(RPI_FIRMWARE_TAG)/boot/bcm2712-rpi-5-b.dtb
RPI5_DTB ?= build/cache/raspi-$(RPI_FIRMWARE_TAG)/bcm2712-rpi-5-b.dtb
@@ -12,6 +12,13 @@
# firmware picks up.
kernel=kernel_2712.img
# Pin the load address to the one the image is linked at
# (kernel/linker/starkernel-native-rpi5.ld). Pi 5 firmware's own default
# for kernel_2712.img is 0x200000, not the classic 0x80000 -- leaving it
# implicit would load a 0x80000-linked flat image 1.5 MiB away from where
# every absolute address in it points.
kernel_address=0x80000
# Disable the firmware's Linux-compatible-image sanity check. Without
# this, official docs describe it as checking for "a compatible Device
# Tree file before attempting to boot" and warn that "older non-compatible
+14
View File
@@ -0,0 +1,14 @@
# Beelink SER5 boot media
`make boot_image TARGET=SER5` builds `build/boards/ser5/lithos-ser5.img`: a GPT disk with one
FAT32 EFI System Partition. Write it to a USB stick with `dd`. The partition holds:
- `EFI/BOOT/BOOTX64.EFI`: the amd64 monolithic loader, with the whole kernel embedded. Firmware
finds it through the UEFI removable-media path, so no boot entry has to be created.
- `startup.nsh`: runs the loader if the firmware drops into the UEFI shell.
- `starforth.cfg`: only present when `KERNEL_ARGS="..."` is given.
Nothing in the image is SER5-specific. It follows the generic UEFI/ACPI path in FABRIC-3.md §III.
Status: booted in QEMU (q35 + OVMF, from this image as its own disk) through POST, with Stadium
conservation exact (sum 65536 = `Q48_ONE`). Not yet booted on the SER5 itself.
+10
View File
@@ -0,0 +1,10 @@
# boards/ser5/board.mk -- Beelink SER5 (AMD Ryzen, x86-64, UEFI/ACPI).
#
# Generic standards-compliant UEFI path (FABRIC-3.md §III): nothing in the
# image is SER5-specific. The monolithic loader embeds the whole kernel, so
# the image is a GPT disk with one FAT32 EFI System Partition holding
# EFI/BOOT/BOOTX64.EFI. Write it to a USB stick with dd.
BOARD_DESC := Beelink SER5 (AMD Ryzen, x86-64, UEFI)
BOARD_ARCH := amd64
BOARD_BOOT := uefi-esp
BOARD_IMAGE := lithos-ser5.img
+17
View File
@@ -0,0 +1,17 @@
# Xilinx Zynq-7020 boot media
The Zynq-7020's processing system is a dual Cortex-A9: ARMv7-A, 32-bit. It is the host for the
StarForth v4 mesh (`docs/v4.0.0/JUSTIFICATION.md` §5, §8).
Boot chain: BootROM, then `BOOT.BIN` (U-Boot SPL with the board's `ps7_init` DDR and clock setup),
then U-Boot, then LithosAnanke. Everything is read from the SD card's FAT partition.
Status: no image yet. `make boot_image TARGET=ZYNQ7020` stops with an error, because there is no
ARMv7 kernel port (`kernel/src/arch/armv7`: entry code, MMU, GIC, private timer). A trial
compile for Cortex-A9 built 147 of the 151 kernel and engine sources. The four that failed use
`__int128` or have no ARMv7 branch: `kernel/src/crypto/fe25519.c`,
`kernel/src/crypto/scalar25519.c`, `kernel/src/hal/hal.c` and
`v3/src/word_source/mixed_arithmetic_words.c`.
`BOOT.BIN` depends on the exact board (Zybo Z7-20, PYNQ-Z2, Arty Z7-20, ZedBoard, ...), because
`ps7_init` holds that board's DDR timing. The board has not been chosen yet.
+11
View File
@@ -0,0 +1,11 @@
# boards/zynq7020/board.mk -- Xilinx Zynq-7020 (dual Cortex-A9, ARMv7-A, 32-bit).
#
# Boot chain: BootROM -> BOOT.BIN (U-Boot SPL with the board's ps7_init DDR/
# clock setup) -> U-Boot -> LithosAnanke, all from the SD card's FAT
# partition. Needs the ARMv7 kernel port (kernel/src/arch/armv7), which is
# in progress: until it exists, boot_image stops with an explicit error
# rather than producing an image that cannot run.
BOARD_DESC := Xilinx Zynq-7020 (Cortex-A9, ARMv7-A, BOOT.BIN) -- ARMv7 port in progress
BOARD_ARCH := armv7
BOARD_BOOT := zynq-bootbin
BOARD_IMAGE := lithos-zynq7020.img
-12
View File
@@ -1,12 +0,0 @@
# Raspberry Pi 5 native boot media
What goes on the SD card for the native (non-UEFI) boot path (FABRIC-3.md §IV):
- `config.txt` — checked in here, done (§IV.3 item 6).
- `bcm2712-rpi-5-b.dtb` — Raspberry Pi firmware's own stock DTB; not built by this
project, copied from the firmware release the card is otherwise built from.
- `kernel_2712.img` — **does not exist yet.** `config.txt` names it, but no build target
in `Makefile.starkernel` currently emits a raw image by this name at load address
0x80000 — item 1 (entry stub) explicitly scoped a separate-image build target as future
work, not part of that item. Building this file is the remaining prerequisite before
item 7 (assembling the card) is possible.
+10
View File
@@ -98,6 +98,16 @@ Block 4007
( rewired: physics-style regression belongs to the kernel )
( exclusively, and this file is deliberately host-portable. )
Block 4008
( HERMES-CHANNEL-OPEN? -- FABRIC-3.6.md task 3.7 hook. )
( Stack: req-hi req-lo -- allow? Kernel-Hermes asks )
( this word, never decides in C, never gates on )
( zuse_session (CLAUDE.md). Default: approve every )
( request. Policy authors edit THIS word's body only -- )
( kernel_hermes.c's own query function never changes. )
: HERMES-CHANNEL-OPEN? ( req-hi req-lo -- allow? )
2DROP 1 ;
Block 4015
( Self-activation - runs after all ACL words are defined )
ACL-BOOT
+120 -163
View File
@@ -1,5 +1,5 @@
# Capsule Block Manifest — Auto-generated
<!-- Generated by mkcapsule --manifest 2026-09-16T19:45:24Z -->
<!-- Generated by mkcapsule --manifest 2026-10-01T19:21:21Z -->
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
<!-- Hand-written justifications and immutability notes live -->
<!-- in MANIFEST.md alongside this auto-generated index. -->
@@ -8,53 +8,52 @@
| Capsule | Blocks claimed | xxHash64 | Signed |
|---------|----------------|----------|--------|
| `ACL.4th` | 4000, 4001, 4002, 4003, 4004, 4005, 4006, 4007, 4015 | `0xd781d22148ff171d` | yes |
| `acl-std79.4th` | 4023, 4024, 4025, 4026, 4027, 4028, 4029, 4030, 4031, 4032, 4033, 4034, 4035, 4036, 4037, 4038, 4039, 4040, 4041, 4042, 4043, 4044, 4045, 4046, 4047, 4048 | `0x773bf9209df191d1` | yes |
| `artemis:init.4th` | 4110, 4111, 4112, 4113, 4122, 4123, 4124, 4125, 4126, 4127, 4128, 4129, 4130, 4131, 4132, 4133, 4134, 4135, 4136, 4137, 4138, 4139, 4140, 4141, 4160, 4161, 4162, 4163, 4164, 4165, 4166, 4167, 4168, 4169, 4170, 4171, 4172, 4173, 4174, 4177, 4178, 4179, 4180, 4181, 4182, 4851, 4852, 4853, 4854, 4856, 4857, 4858, 4860 | `0xf55edc4e76a8c294` | yes |
| `block-acl.4th` | 4019, 4020 | `0xf6cc2a59e3a6734e` | yes |
| `common:messaging.4th` | 5003, 5004, 5005, 5006, 5007, 5008, 5009, 5010, 5011, 5012, 5013, 5014, 5015, 5016, 5017, 5018, 5019, 5020, 5021, 5022, 5023, 5024, 5025, 5026, 5027, 5028, 5029, 5030, 5031, 5032, 5033, 5034, 5035, 5036, 5037, 5038, 5039, 5040, 5041, 5042 | `0x201cfd6d39fcb22d` | yes |
| `common:msg.4th` | 4055 | `0x850a0382344ea6c4` | yes |
| `doe-campaign.4th` | 4060, 4061, 4062, 4063, 4064, 4065 | `0x3d4549142d91ec20` | yes |
| `doe.4th` | 2100, 2101, 2102, 2103, 2104, 2105, 2106, 2107 | `0xf154616d248e861f` | yes |
| `fabric.4th` | 4900, 4901, 4902, 4903, 4904, 4905, 4906, 4907, 4908, 4909, 4910, 4911, 4912, 4913, 4914, 4915, 4916, 4917, 4918, 4919, 4920, 4921, 4922, 4923, 4924, 5000, 5001, 5002 | `0x9d9489cbeca4099b` | yes |
| `font.4th` | 4925, 4926, 4927, 4928, 4929, 4930, 4931, 4932, 4933, 4934, 4935, 4936, 4937, 4938, 4939, 4940, 4941, 4942, 4943, 4944, 4945, 4946, 4947, 4948, 4949, 4950, 4951, 4952, 4953, 4954, 4955, 4956, 4957, 4958, 4959, 4960, 4961, 4962, 4963, 4964, 4965, 4966, 4967, 4968, 4969, 4970, 4971, 4972, 4973, 4974, 4975, 4976, 4977, 4978, 4979, 4980, 4981, 4982, 4983, 4984, 4985 | `0x3f305911500c78f6` | yes |
| `hermes:init.4th` | 4153, 4855, 5116 | `0x2df61924448a6812` | yes |
| `init-l8-diverse.4th` | 4820, 4821, 4822 | `0xaa293201a6c91838` | yes |
| `init-l8-omni.4th` | 2064, 2065, 2066, 2067, 2068, 2069, 2070, 2071, 2072, 2073, 2074, 2075, 2076, 2077, 2078, 2079 | `0x5979e314d6452045` | yes |
| `init-l8-stable.4th` | 4806 | `0xdc3830f189063a9a` | yes |
| `init-l8-temporal.4th` | 4830, 4831 | `0x51abd4c138246651` | yes |
| `init-l8-transition.4th` | 4840, 4841, 4842 | `0xbcc1a81976f0a4c9` | yes |
| `init-l8-volatile.4th` | 4810, 4811, 4812, 4813 | `0x98caabbbd92abac4` | yes |
| `init.4th` | 2049, 2050, 2057 | `0x1ef4939ed32ec1e6` | yes |
| `lib.4th` | 4050 | `0x4b216635c359ef73` | yes |
| `multiuser-doe.4th` | 5044, 5045, 5046, 5047, 5048, 5049, 5050, 5051, 5052, 5053, 5054, 5055 | `0x22140588ee7c39e6` | yes |
| `process.4th` | 4300, 4301 | `0x781afc1dbd0294f7` | yes |
| `sdk.4th` | 5109, 5110, 5111, 5112, 5113, 5114, 5115 | `0x008fdbbb62c94a3a` | yes |
| `turtle.4th` | 5100, 5101, 5102, 5103, 5104, 5105, 5106, 5107, 5108 | `0x4d470418ca543365` | yes |
| `user-font-demo.4th` | 4200, 4201, 4202 | `0xce1fd7d1b581a56d` | yes |
| `workload-0.4th` | 2200, 2201 | `0x93f86f60aeba8feb` | yes |
| `workload-1-lite.4th` | 5058, 5059 | `0x44a7a7e3176dcc8d` | yes |
| `workload-1.4th` | 4406, 4415, 4425, 4435 | `0x63e251adb0a03613` | yes |
| `workload-2.4th` | 4506, 4515, 4525, 4535, 4545 | `0xf113b3d0bcccae47` | yes |
| `workload-3.4th` | 4606, 4615, 4625, 4635, 4645, 4655, 4665 | `0x62b7a71576ad1041` | yes |
| `workload-4.4th` | 2130, 2131, 2132 | `0x099619264e650b1c` | yes |
| `workload-5-lite.4th` | 5056, 5057 | `0xea0303f38824f254` | yes |
| `workload-5.4th` | *(none — raw code capsule)* | `0x526964c7c9506a11` | yes |
| `workload-6.4th` | 2080, 2081, 2082, 2083, 2084, 2085, 2086, 2087, 2088, 2089, 2090, 2091, 2092, 2093, 2094, 2095 | `0x06fc0ce1e369ef5a` | yes |
| `workload-7.4th` | 2150 | `0x02862291387eb7ac` | yes |
| `workload-8.4th` | 2160 | `0x56b7f2f0efa000df` | yes |
| `workload-9.4th` | 4706, 4715, 4725, 4735, 4745 | `0x3f2bec73142aa424` | yes |
| `workload-calib1.4th` | 5043 | `0x3b9f2d17b554fabc` | yes |
| `zuse-eligibility.4th` | 4021, 4022 | `0x8b49c1bc1e01dc58` | yes |
| `zuse.4th` | 4016, 4017, 4018 | `0x490ded9be257a90b` | yes |
| `ACL.4th` | 4000, 4001, 4002, 4003, 4004, 4005, 4006, 4007, 4008, 4015 | `0xf8890c05c0d8f921` | n/a |
| `acl-std79.4th` | 4023, 4024, 4025, 4026, 4027, 4028, 4029, 4030, 4031, 4032, 4033, 4034, 4035, 4036, 4037, 4038, 4039, 4040, 4041, 4042, 4043, 4044, 4045, 4046, 4047, 4048 | `0x773bf9209df191d1` | n/a |
| `artemis:init.4th` | 4110, 4111, 4112, 4113, 4122, 4123, 4124, 4125, 4126, 4127, 4128, 4129, 4130, 4131, 4132, 4133, 4134, 4135, 4136, 4137, 4138, 4139, 4140, 4141, 4160, 4161, 4162, 4163, 4164, 4165, 4166, 4167, 4168, 4169, 4170, 4171, 4172, 4173, 4174, 4177, 4178, 4179, 4180, 4181, 4182, 4851, 4852, 4853, 4854, 4856, 4857, 4858, 4860 | `0xe6fba9ae56e1c916` | n/a |
| `block-acl.4th` | 4019, 4020 | `0xf6cc2a59e3a6734e` | n/a |
| `doe-campaign.4th` | 4060, 4061, 4062, 4063, 4064, 4065 | `0x26fb485e5c9dc6ff` | n/a |
| `doe.4th` | 2100, 2101, 2102, 2103, 2104, 2105, 2106, 2107 | `0xf154616d248e861f` | n/a |
| `fabric.4th` | 4900, 4901, 4902, 4903, 4904, 4905, 4906, 4907, 4908, 4909, 4910, 4911, 4912, 4913, 4914, 4915, 4916, 4917, 4918, 4919, 4920, 4921, 4922, 4923, 4924, 5000, 5001, 5002 | `0x9d9489cbeca4099b` | n/a |
| `font.4th` | 4925, 4926, 4927, 4928, 4929, 4930, 4931, 4932, 4933, 4934, 4935, 4936, 4937, 4938, 4939, 4940, 4941, 4942, 4943, 4944, 4945, 4946, 4947, 4948, 4949, 4950, 4951, 4952, 4953, 4954, 4955, 4956, 4957, 4958, 4959, 4960, 4961, 4962, 4963, 4964, 4965, 4966, 4967, 4968, 4969, 4970, 4971, 4972, 4973, 4974, 4975, 4976, 4977, 4978, 4979, 4980, 4981, 4982, 4983, 4984, 4985 | `0x3f305911500c78f6` | n/a |
| `hestia:init.4th` | 4986, 4987, 4988 | `0x64e111990fbc45ff` | n/a |
| `init-l8-diverse.4th` | 4820, 4821, 4822 | `0xaa293201a6c91838` | n/a |
| `init-l8-omni.4th` | 2064, 2065, 2066, 2067, 2068, 2069, 2070, 2071, 2072, 2073, 2074, 2075, 2076, 2077, 2078, 2079 | `0x5979e314d6452045` | n/a |
| `init-l8-stable.4th` | 4806 | `0xdc3830f189063a9a` | n/a |
| `init-l8-temporal.4th` | 4830, 4831 | `0x51abd4c138246651` | n/a |
| `init-l8-transition.4th` | 4840, 4841, 4842 | `0xbcc1a81976f0a4c9` | n/a |
| `init-l8-volatile.4th` | 4810, 4811, 4812, 4813 | `0x98caabbbd92abac4` | n/a |
| `init.4th` | 2049, 2050, 2057 | `0xea038ba684c53443` | n/a |
| `lib.4th` | 4050 | `0x4b216635c359ef73` | n/a |
| `multiuser-doe.4th` | 5044, 5045, 5046, 5047, 5048, 5049, 5050, 5051, 5052, 5053, 5054, 5055 | `0x22140588ee7c39e6` | n/a |
| `sdk.4th` | 5109, 5110, 5111, 5112, 5113, 5114, 5115 | `0x008fdbbb62c94a3a` | n/a |
| `turtle.4th` | 5100, 5101, 5102, 5103, 5104, 5105, 5106, 5107, 5108 | `0x4d470418ca543365` | n/a |
| `user-font-demo.4th` | 4200, 4201, 4202 | `0xce1fd7d1b581a56d` | n/a |
| `workload-0.4th` | 2200, 2201 | `0x93f86f60aeba8feb` | n/a |
| `workload-1-lite.4th` | 5058, 5059 | `0x44a7a7e3176dcc8d` | n/a |
| `workload-1.4th` | 4406, 4415, 4425, 4435 | `0x63e251adb0a03613` | n/a |
| `workload-2.4th` | 4506, 4515, 4525, 4535, 4545 | `0xf113b3d0bcccae47` | n/a |
| `workload-3.4th` | 4606, 4615, 4625, 4635, 4645, 4655, 4665 | `0x62b7a71576ad1041` | n/a |
| `workload-4.4th` | 2130, 2131, 2132 | `0x099619264e650b1c` | n/a |
| `workload-5-lite.4th` | 5056, 5057 | `0xea0303f38824f254` | n/a |
| `workload-5.4th` | *(none — raw code capsule)* | `0x526964c7c9506a11` | n/a |
| `workload-6.4th` | 2080, 2081, 2082, 2083, 2084, 2085, 2086, 2087, 2088, 2089, 2090, 2091, 2092, 2093, 2094, 2095 | `0x06fc0ce1e369ef5a` | n/a |
| `workload-7.4th` | 2150 | `0x02862291387eb7ac` | n/a |
| `workload-8.4th` | 2160 | `0x56b7f2f0efa000df` | n/a |
| `workload-9.4th` | 4706, 4715, 4725, 4735, 4745 | `0x3f2bec73142aa424` | n/a |
| `workload-calib1.4th` | 5043 | `0x3b9f2d17b554fabc` | n/a |
| `zuse-eligibility.4th` | 4021, 4022 | `0x7b28f4776a32e0b7` | n/a |
| `zuse.4th` | 4016, 4017, 4018 | `0xa14d5a7a88791cf6` | n/a |
*Signed column is `n/a`: this manifest run had no `--sign-key`. Re-run with `--sign-key <path>` to check signing status (does not modify or require rebuilding capsule_generated.c).*
## Block Map (sorted by LBN)
| LBN | Capsule | xxHash64 | Status |
|-----|---------|----------|--------|
| 2049 | `init.4th` | `0x1ef4939ed32ec1e6` | ok |
| 2050 | `init.4th` | `0x1ef4939ed32ec1e6` | ok |
| 2057 | `init.4th` | `0x1ef4939ed32ec1e6` | ok |
| 2049 | `init.4th` | `0xea038ba684c53443` | ok |
| 2050 | `init.4th` | `0xea038ba684c53443` | ok |
| 2057 | `init.4th` | `0xea038ba684c53443` | ok |
| 2064 | `init-l8-omni.4th` | `0x5979e314d6452045` | ok |
| 2065 | `init-l8-omni.4th` | `0x5979e314d6452045` | ok |
| 2066 | `init-l8-omni.4th` | `0x5979e314d6452045` | ok |
@@ -102,22 +101,23 @@
| 2160 | `workload-8.4th` | `0x56b7f2f0efa000df` | ok |
| 2200 | `workload-0.4th` | `0x93f86f60aeba8feb` | ok |
| 2201 | `workload-0.4th` | `0x93f86f60aeba8feb` | ok |
| 4000 | `ACL.4th` | `0xd781d22148ff171d` | ok |
| 4001 | `ACL.4th` | `0xd781d22148ff171d` | ok |
| 4002 | `ACL.4th` | `0xd781d22148ff171d` | ok |
| 4003 | `ACL.4th` | `0xd781d22148ff171d` | ok |
| 4004 | `ACL.4th` | `0xd781d22148ff171d` | ok |
| 4005 | `ACL.4th` | `0xd781d22148ff171d` | ok |
| 4006 | `ACL.4th` | `0xd781d22148ff171d` | ok |
| 4007 | `ACL.4th` | `0xd781d22148ff171d` | ok |
| 4015 | `ACL.4th` | `0xd781d22148ff171d` | ok |
| 4016 | `zuse.4th` | `0x490ded9be257a90b` | ok |
| 4017 | `zuse.4th` | `0x490ded9be257a90b` | ok |
| 4018 | `zuse.4th` | `0x490ded9be257a90b` | ok |
| 4000 | `ACL.4th` | `0xf8890c05c0d8f921` | ok |
| 4001 | `ACL.4th` | `0xf8890c05c0d8f921` | ok |
| 4002 | `ACL.4th` | `0xf8890c05c0d8f921` | ok |
| 4003 | `ACL.4th` | `0xf8890c05c0d8f921` | ok |
| 4004 | `ACL.4th` | `0xf8890c05c0d8f921` | ok |
| 4005 | `ACL.4th` | `0xf8890c05c0d8f921` | ok |
| 4006 | `ACL.4th` | `0xf8890c05c0d8f921` | ok |
| 4007 | `ACL.4th` | `0xf8890c05c0d8f921` | ok |
| 4008 | `ACL.4th` | `0xf8890c05c0d8f921` | ok |
| 4015 | `ACL.4th` | `0xf8890c05c0d8f921` | ok |
| 4016 | `zuse.4th` | `0xa14d5a7a88791cf6` | ok |
| 4017 | `zuse.4th` | `0xa14d5a7a88791cf6` | ok |
| 4018 | `zuse.4th` | `0xa14d5a7a88791cf6` | ok |
| 4019 | `block-acl.4th` | `0xf6cc2a59e3a6734e` | ok |
| 4020 | `block-acl.4th` | `0xf6cc2a59e3a6734e` | ok |
| 4021 | `zuse-eligibility.4th` | `0x8b49c1bc1e01dc58` | ok |
| 4022 | `zuse-eligibility.4th` | `0x8b49c1bc1e01dc58` | ok |
| 4021 | `zuse-eligibility.4th` | `0x7b28f4776a32e0b7` | ok |
| 4022 | `zuse-eligibility.4th` | `0x7b28f4776a32e0b7` | ok |
| 4023 | `acl-std79.4th` | `0x773bf9209df191d1` | ok |
| 4024 | `acl-std79.4th` | `0x773bf9209df191d1` | ok |
| 4025 | `acl-std79.4th` | `0x773bf9209df191d1` | ok |
@@ -145,64 +145,60 @@
| 4047 | `acl-std79.4th` | `0x773bf9209df191d1` | ok |
| 4048 | `acl-std79.4th` | `0x773bf9209df191d1` | ok |
| 4050 | `lib.4th` | `0x4b216635c359ef73` | ok |
| 4055 | `common:msg.4th` | `0x850a0382344ea6c4` | ok |
| 4060 | `doe-campaign.4th` | `0x3d4549142d91ec20` | ok |
| 4061 | `doe-campaign.4th` | `0x3d4549142d91ec20` | ok |
| 4062 | `doe-campaign.4th` | `0x3d4549142d91ec20` | ok |
| 4063 | `doe-campaign.4th` | `0x3d4549142d91ec20` | ok |
| 4064 | `doe-campaign.4th` | `0x3d4549142d91ec20` | ok |
| 4065 | `doe-campaign.4th` | `0x3d4549142d91ec20` | ok |
| 4110 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4111 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4112 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4113 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4122 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4123 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4124 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4125 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4126 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4127 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4128 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4129 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4130 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4131 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4132 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4133 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4134 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4135 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4136 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4137 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4138 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4139 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4140 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4141 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4153 | `hermes:init.4th` | `0x2df61924448a6812` | ok |
| 4160 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4161 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4162 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4163 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4164 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4165 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4166 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4167 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4168 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4169 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4170 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4171 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4172 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4173 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4174 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4177 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4178 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4179 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4180 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4181 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4182 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4060 | `doe-campaign.4th` | `0x26fb485e5c9dc6ff` | ok |
| 4061 | `doe-campaign.4th` | `0x26fb485e5c9dc6ff` | ok |
| 4062 | `doe-campaign.4th` | `0x26fb485e5c9dc6ff` | ok |
| 4063 | `doe-campaign.4th` | `0x26fb485e5c9dc6ff` | ok |
| 4064 | `doe-campaign.4th` | `0x26fb485e5c9dc6ff` | ok |
| 4065 | `doe-campaign.4th` | `0x26fb485e5c9dc6ff` | ok |
| 4110 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4111 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4112 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4113 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4122 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4123 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4124 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4125 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4126 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4127 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4128 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4129 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4130 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4131 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4132 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4133 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4134 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4135 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4136 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4137 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4138 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4139 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4140 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4141 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4160 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4161 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4162 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4163 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4164 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4165 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4166 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4167 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4168 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4169 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4170 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4171 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4172 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4173 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4174 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4177 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4178 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4179 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4180 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4181 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4182 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4200 | `user-font-demo.4th` | `0xce1fd7d1b581a56d` | ok |
| 4201 | `user-font-demo.4th` | `0xce1fd7d1b581a56d` | ok |
| 4202 | `user-font-demo.4th` | `0xce1fd7d1b581a56d` | ok |
| 4300 | `process.4th` | `0x781afc1dbd0294f7` | ok |
| 4301 | `process.4th` | `0x781afc1dbd0294f7` | ok |
| 4406 | `workload-1.4th` | `0x63e251adb0a03613` | ok |
| 4415 | `workload-1.4th` | `0x63e251adb0a03613` | ok |
| 4425 | `workload-1.4th` | `0x63e251adb0a03613` | ok |
@@ -237,15 +233,14 @@
| 4840 | `init-l8-transition.4th` | `0xbcc1a81976f0a4c9` | ok |
| 4841 | `init-l8-transition.4th` | `0xbcc1a81976f0a4c9` | ok |
| 4842 | `init-l8-transition.4th` | `0xbcc1a81976f0a4c9` | ok |
| 4851 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4852 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4853 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4854 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4855 | `hermes:init.4th` | `0x2df61924448a6812` | ok |
| 4856 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4857 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4858 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4860 | `artemis:init.4th` | `0xf55edc4e76a8c294` | ok |
| 4851 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4852 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4853 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4854 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4856 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4857 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4858 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4860 | `artemis:init.4th` | `0xe6fba9ae56e1c916` | ok |
| 4900 | `fabric.4th` | `0x9d9489cbeca4099b` | ok |
| 4901 | `fabric.4th` | `0x9d9489cbeca4099b` | ok |
| 4902 | `fabric.4th` | `0x9d9489cbeca4099b` | ok |
@@ -332,49 +327,12 @@
| 4983 | `font.4th` | `0x3f305911500c78f6` | ok |
| 4984 | `font.4th` | `0x3f305911500c78f6` | ok |
| 4985 | `font.4th` | `0x3f305911500c78f6` | ok |
| 4986 | `hestia:init.4th` | `0x64e111990fbc45ff` | ok |
| 4987 | `hestia:init.4th` | `0x64e111990fbc45ff` | ok |
| 4988 | `hestia:init.4th` | `0x64e111990fbc45ff` | ok |
| 5000 | `fabric.4th` | `0x9d9489cbeca4099b` | ok |
| 5001 | `fabric.4th` | `0x9d9489cbeca4099b` | ok |
| 5002 | `fabric.4th` | `0x9d9489cbeca4099b` | ok |
| 5003 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
| 5004 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
| 5005 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
| 5006 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
| 5007 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
| 5008 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
| 5009 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
| 5010 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
| 5011 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
| 5012 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
| 5013 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
| 5014 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
| 5015 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
| 5016 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
| 5017 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
| 5018 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
| 5019 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
| 5020 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
| 5021 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
| 5022 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
| 5023 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
| 5024 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
| 5025 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
| 5026 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
| 5027 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
| 5028 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
| 5029 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
| 5030 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
| 5031 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
| 5032 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
| 5033 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
| 5034 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
| 5035 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
| 5036 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
| 5037 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
| 5038 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
| 5039 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
| 5040 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
| 5041 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
| 5042 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
| 5043 | `workload-calib1.4th` | `0x3b9f2d17b554fabc` | ok |
| 5044 | `multiuser-doe.4th` | `0x22140588ee7c39e6` | ok |
| 5045 | `multiuser-doe.4th` | `0x22140588ee7c39e6` | ok |
@@ -408,11 +366,10 @@
| 5113 | `sdk.4th` | `0x008fdbbb62c94a3a` | ok |
| 5114 | `sdk.4th` | `0x008fdbbb62c94a3a` | ok |
| 5115 | `sdk.4th` | `0x008fdbbb62c94a3a` | ok |
| 5116 | `hermes:init.4th` | `0x2df61924448a6812` | ok |
## Conflicts
None.
---
*39 capsule(s) scanned. Re-run `mkcapsule --manifest <dir>` to refresh.*
*36 capsule(s) scanned. Re-run `mkcapsule --manifest <dir>` to refresh.*
+87 -74
View File
@@ -49,7 +49,7 @@ Blocks: **2049–2052, 2057**
| Block | Immutable | Justification |
|-------|-----------|---------------|
| 2049 | No | Hera VM init: loads compudynamics, VM-INIT, lib, common:msg, fleet-k, process; BIRTHs Artemis + Hermes. VM-TREE + VM-CHILDREN implemented (`b52281b9`), unrolled for v1 Tripod |
| 2049 | No | Hera VM init: `VM-TREE`/`VM-PARENT`/`VM-CHILDREN`; loads `ACL.4th`, `block-acl.4th`, `zuse-eligibility.4th`, `lib.4th`. **Corrected 2026-09-19 (three times)** — first pass removed a stale listing of `compudynamics`/`common:msg`/`fleet-k`/`process` (none loaded here; two already-deleted, two stripped in the same pass, FABRIC-3.6.md tasks 0.2/0.3). Second pass (same day): `fabric.4th`/`font.4th` **removed** from this list — relocated to `hestia/init.4th` (FABRIC-3.6.md tasks 1.6/1.7), Hera no longer loads either. Third pass (2026-09-22, FABRIC-3.6.md Phase 4 Stage E): `common:messaging.4th` + `MSG-CD-INIT` **removed** — the file itself is deleted (see "Deleted capsules" below); every FORTH-owned message type was cut over to kernel-Hermes first (Phase 3) |
| 2050 | No | BOOT-BANNER call — separated so the banner block can be swapped without touching init logic |
| 2051 | No | TRIPOD-TEST — 6 acceptance gates per TRIPOD.md. Dead EVENT-WAIT step replaced with HERMES-TICK liveness check (`3436d564`) |
| 2052 | No | TRIPOD-TEST invocation block |
@@ -308,14 +308,6 @@ Blocks: **4050**
|-------|-----------|---------------|
| 4050 | No | N. COMMA CRLF CSV-COL CSV-LAST Q.SHOW USE RUN. Not immutable: these are utilities; adding words is safe. Removing or renaming existing words requires audit of all callers |
### `common/msg.4th` — Hermes participant interface
Blocks: **4055**
| Block | Immutable | Justification |
|-------|-----------|---------------|
| 4055 | Yes | HERMES-ACK / HERMES-NACK. Immutable: this is the cross-VM ACK/NACK ABI. Every messaging VM (Hera, Hermes, Artemis) loads this at birth. Changing the block or the word names breaks the Hermes delivery protocol |
### `doe-campaign.4th` — DoE campaign harness
Blocks: **4060–4065**
@@ -325,74 +317,51 @@ Blocks: **4060–4065**
matched the file at any point on record. Rewritten to match the actual
content, migrated this pass off the deleted `compudynamics.4th`/
`fleet-k.4th` primitives onto the `VM-PHYSICS-STATUS`/`VM-CONSERVED?`
mechanism — see `capsules/hermes/init.4th` block 4153 and
mechanism — see (historical) `hermes/init.4th` block 4153 and
`capsules/artemis/init.4th` block 4852 for the companion `LOAD-DOE` fix
this migration also required.
**Note (2026-09-22, FABRIC-3.6.md Phase 4 Stage E):** `SETUP-HERMES` and every
Hermes touch in `PHASE1-DOE`/`CD-TICK`/`SMOKE-CAMPAIGN` removed — Hermes no
longer exists. Every block below is now Artemis-only.
| Block | Immutable | Justification |
|-------|-----------|---------------|
| 4060 | No | `SETUP-HERMES`/`SETUP-ARTEMIS`/`SETUP-VMS`: `BIRTH` + remote `LOAD-DOE` VM-EXEC per child VM. Not immutable: setup sequence may grow |
| 4061 | No | `PHASE1-DOE`: real per-VM `DOE-WORK` baseline touches. Historical note: dead `CD-WORK` (index-dispatch, deleted VM-HERA/HERMES/ARTEMIS constants) removed here 2026-07-08 |
| 4062 | No | `CD-TICK`/`CD-DOE`: one real fleet-touch pass (`VM-EXEC "DOE-WORK"` on Hermes+Artemis), looped N times. `CAMPAIGN-STATUS`: `VM-PHYSICS-STATUS` + `VM-CONSERVED?` check. Not immutable: workload/status content may evolve |
| 4060 | No | `SETUP-ARTEMIS`/`SETUP-VMS`: `BIRTH` + remote `LOAD-DOE` VM-EXEC on Artemis. Not immutable: setup sequence may grow |
| 4061 | No | `PHASE1-DOE`: real per-VM `DOE-WORK` baseline touch, Artemis only. Historical note: dead `CD-WORK` (index-dispatch, deleted VM-HERA/HERMES/ARTEMIS constants) removed here 2026-07-08 |
| 4062 | No | `CD-TICK`/`CD-DOE`: one real fleet-touch pass (`VM-EXEC "DOE-WORK"` on Artemis), looped N times. `CAMPAIGN-STATUS`: `VM-PHYSICS-STATUS` + `VM-CONSERVED?` check. Not immutable: workload/status content may evolve |
| 4063 | No | `CAMPAIGN`: full campaign — baselines then 30 real fleet touches |
| 4064 | No | `SMOKE-CAMPAIGN`: 1-rep seed-1959 smoke test + 16 real fleet touches |
| 4065 | No | `THREE-VM-CAMPAIGN`: loads `doe.4th` locally on Hera, then 48 real fleet touches, all three VMs as Compudynamics peers |
### `hermes/init.4th` — Hermes VM (messenger)
### `hermes/init.4th` — DELETED 2026-09-22 (formerly Hermes VM, the messenger)
Blocks: **4100–4109, 4114–4121, 4142–4153**
**Deleted, FABRIC-3.6.md Phase 4 Stage E (task 4.1).** Formerly blocks
**4100–4109, 4114–4121, 4142–4153** (gaps 4110–4113 and 4122–4141 were always
Artemis's, hard-locked, never Hermes's — see the `artemis/init.4th` entry below,
unaffected by this deletion). Every FORTH-owned message type this file's
`messaging.4th` companion implemented (`BLK-ATTACH-EVENT`, `CONSOLE-CMD-EVENT`,
`ELEVATE-REQUEST`) was cut over to kernel-Hermes first (Phase 3, tasks 3.8–3.10),
verified live on all three architectures at each step; only then was the Hermes
VM itself, this capsule, and `capsules/common/messaging.4th` removed. Full
three-architecture acceptance for the deletion: `logs/20260922-181141/amd64/`,
`logs/20260922-181753/aarch64/`, `logs/20260922-182136/riscv64/` — zero
`UNKNOWN WORD`, identical `dict_hash` across all three. See FABRIC-3.6.md's own
Phase 4 entry for the full account, including one found-not-fixed collateral
defect (`SEND-ELEVATE-REQUEST`, this file's own former caller into
`zuse-eligibility.4th`'s `ELEVATE-GRANT`, is now unreachable — Captain Bob's
2026-09-22 decision: leave it, Phase 8 PKI will build its own entrypoint).
Former blocks 4100–4153 (ex-Artemis-gaps) are now UNASSIGNED — see Unassigned
Ranges below. The per-block justification table this section used to carry
(arena allocator ABI, message/channel field accessors, the VM-name routing
table, channel negotiation, broadcast) is not reproduced here; it is historical
detail about deleted code, recoverable from git history (`git show
<pre-strip-commit>:capsules/hermes/init.4th`) if ever needed again.
Gap 4110–4113 = Artemis. **HARD LOCKED. Never touch from Hermes side.**
Gap 4122–4141 = Artemis (extended — see below). Never touch from Hermes side.
| Block | Immutable | Justification |
|-------|-----------|---------------|
| 4100 | Yes | Hermes constants: event codes, channel states, node sizes, arena sizes, MSG-DELIVERED. Immutable: changing node size constants corrupts all arena math |
| 4101 | Yes | Arena CREATE: MSG-ARENA CH-ARENA MBR-ARENA; free-list roots; MSG-SEQ CH-ACTIVE. Immutable: CREATE allocates memory at compile time; re-running changes VM memory layout |
| 4102 | Yes | MSG-INIT-FREE + CH-INIT-FREE. Immutable: free-list init is called once from CD-INIT; structure change breaks allocator |
| 4103 | Yes | MBR-INIT-FREE + MSG-ALLOC + MSG-FREE-NODE. Immutable: allocator ABI; MSG-ALLOC is called by every MSG-SEND |
| 4104 | Yes | CH-ALLOC + CH-FREE-NODE + MBR-ALLOC + MBR-FREE-NODE. Immutable: same allocator ABI constraint |
| 4105 | Yes | Message field accessors (MSG-TYPE@/! through MSG-SEQ@/!). Immutable: field offsets are load-bearing; changing breaks every accessor caller |
| 4106 | Yes | Channel + member accessors (CH-ID@/! through MBR-VM@). Immutable: same field offset constraint |
| 4107 | No | VARIABLE MSG-LAST-MSG + IDX>NAME + MSG-DELIVER. Not fully immutable: MSG-DELIVER may evolve for async model. IDX>NAME now a table lookup over VM-NAME-ADDRS/LENS (see 4142) |
| 4108 | No | MSG-SEND. Not immutable: send protocol may evolve |
| 4109 | No | MSG-REAP. Ordering bug fixed (`8ca0cda2`) — type cleared before free-node prepend |
| — | — | 4110–4113: ARTEMIS. NEVER TOUCH. |
| 4114 | No | VARIABLE CH-SCAN + CH-COOL-ALL + CH-TOTAL-HEAT. Not immutable: channel cooling may evolve |
| 4115 | No | CH-REAP-SAFE. Not immutable: channel reaping logic may evolve |
| 4116 | No | VARIABLE COMMON-CH + COMMON-INIT + HERMES-TICK. Not immutable: HERMES-TICK drives the event loop; may grow |
| 4117 | No | EVENT-EMIT + EVENT-WAIT + EVENT-DRAIN (backward compat). Not immutable: EVENT-WAIT is a diagnostic peek only per HERMES.md |
| — | — | 4118: reserved, currently empty (former HERA-NOTIFY-SPAWN/KILL, removed `9323f776` — notification is automatic via `vm_physics_init`/`retire`) |
| 4119 | No | CH-MINT-ID + CH-REQUEST. Not immutable: channel negotiation protocol may expand (multi-party invite deferred) |
| 4120 | No | CD-INIT. Not immutable: init sequence may grow |
| 4121 | No | MSG-ACK-LAST + MSG-NACK-LAST. Not immutable: NACK-requeue (reduced heat, retry) deferred; block may need extension |
| — | — | 4122–4141: ARTEMIS (extended). NEVER TOUCH. |
| 4142 | No | VM name routing table: VM-NAME-ADDRS/LENS + VM-NAME-REG + VM-NAMES-INIT. Not immutable: table-driven, grows via VM-MAX |
| 4143 | Yes | MSG-CH@/! + MSG-ORIG-TYPE@/! + MSG-NACKED. Immutable: field offset constraint, same as 4105 |
| 4144 | No | MSG-SEND. Not immutable: send protocol may evolve |
| 4145 | No | MSG-TOTAL-HEAT + MSG-REDELIVER-NACKED. Not immutable: cooling/redelivery logic may evolve |
| 4146 | No | MSG-DELIVER-ALL. Not immutable: delivery logic may evolve |
| 4147 | No | HERMES-K + WELCOME. Not immutable: may grow |
| 4148 | No | CH-ACCEPT + CH-CONFIRM + CH-CLOSE. Not immutable: channel negotiation protocol may expand |
| 4149 | No | HERMES-STATUS (MSG-USED + CH-USED). Not immutable: status may expand |
| 4150 | No | Member management: MBR-NEXT!/VM! + CH-ADD-MBR + HERMES-MSG-TEST. Not immutable: may grow |
| 4151 | No | Phase 2 real multi-member broadcast: MSG-BROADCAST + BC-* variables. Not immutable: broadcast protocol may expand |
| 4152 | No | Phase 2 COMMON-CH membership + test: BCAST-GOT/BCAST-RECV/REGISTER-COMMON-MEMBERS/SEND-BROADCAST-TEST. Not immutable: test harness may grow |
| 4153 | No | `LOAD-DOE` ( -- ): `S" doe.4th" EXEC`. Added 2026-07-08 — `doe-campaign.4th`'s `SETUP-HERMES` calls this remotely so Hermes gets `DOE-WORK`; was missing entirely before this pass, so `doe-campaign.4th` could never have worked even pre-dating the VM-fleet-physics redesign. Not immutable: trivial wrapper |
**2026-07-05 collision fix:** blocks 4142–4150 were previously numbered
4122–4131 (interleaved with the blocks below them in this list), directly
colliding with Artemis's real block range. The collision was introduced
`fca7b09a` (Jul 1) when the VM-name routing table was added without
checking MANIFEST.md, and went undetected for 4 days because `mkcapsule`'s
build path doesn't run conflict detection (only `--manifest` does). Found
via `mkcapsule --manifest` while syncing this document; renumbered into
genuinely free space and reverified with a clean amd64 TRIPOD-TEST boot
(`PASS: fleet K`, `PASS: Hermes liveness`, `PASS: Artemis ready`, `K soak`,
`PASS: E2E msg flow` — Hermes's kill/rebirth soak test reproduced an
identical `dict_hash` before and after, confirming the reload path is now
clean). amd64-only per the rev-f iteration rule; three-arch not yet run for
this fix. See Conflict Register entry C5.
**2026-07-05 collision fix (historical, still relevant to Conflict Register
C5 below):** blocks 4142–4150 were previously numbered 4122–4131, directly
colliding with Artemis's real block range, introduced `fca7b09a` (Jul 1),
found and resolved via `mkcapsule --manifest`. See Conflict Register entry C5.
### `artemis/init.4th` — Artemis VM (flat pool disk manager v2)
@@ -439,14 +408,19 @@ Hermes (see above). The table below reflects the actual current footprint.
| 4851 | No | ART-PING ( -- ): cheap O(1) touch target for Phase 3 fleet DoE workload, unlike ART-TICK/ART-STATUS which scan all ART-DATA-BLKS. Not immutable: may extend |
| 4852 | No | `LOAD-DOE` ( -- ): `S" doe.4th" EXEC`. Added 2026-07-08 — same missing-word fix as `hermes/init.4th` 4153; `doe-campaign.4th`'s `SETUP-ARTEMIS` calls this remotely. Not immutable: trivial wrapper |
### `process.4th` — VM process management
### `hestia/init.4th` — Hestia VM (third Tripod leg, bind point)
Blocks: **4300–4301**
Blocks: **4986–4988** (allocated 4986–4996, FABRIC-3.6.md task 1.1; 3 of the 11 used so far)
Added 2026-09-19 (FABRIC-3.6.md tasks 1.2/1.4/1.6/1.7, Tripod/kernel reshuffle — see
FABRIC-3.5.md §II/§IV). Reconstitutes the Tripod as Hera/Artemis/Hestia. Birthed (task 1.4)
and owns the drawing fabric (`fabric.4th`/`font.4th`, tasks 1.6/1.7 — see block 4988).
| Block | Immutable | Justification |
|-------|-----------|---------------|
| 4300 | No | Event code constants + SPAWN + PAUSE lifecycle operators. Not immutable: process model may evolve |
| 4301 | No | RESUME + KILL-VM. Not immutable: process model may evolve |
| 4986 | No | `WELCOME` banner word + call. Not immutable: message text may change |
| 4988 | No | `S" fabric.4th" EXEC` + `S" font.4th" EXEC` — moved here from `init.4th` (Hera), tasks 1.6/1.7, **merged into one commit rather than done separately as the punchlist originally split them**: `font.4th` calls `G-LINE`/`G-ELLIPSE`, which are `fabric.4th`'s own words, so moving one without the other strands the dependency in whichever VM keeps only half — confirmed live, moving `fabric.4th` alone floods Hera's boot with `UNKNOWN WORD: 'G-LINE'`. Order preserved (`fabric.4th` before `font.4th`), matching `init.4th`'s own prior load order. Not immutable: may grow if more of the drawing/font surface relocates here |
| 4987 | No | **Rewritten 2026-09-22, FABRIC-3.6.md Phase 4 Stage E.** Originally joined `COMMON-CH` on slot 11 (Hera/Hermes/Artemis held 0/1/2, identities held 3–10, `messaging.4th:78-85`) — that whole `COMMON-CH` concept no longer exists, `messaging.4th` is deleted. Now just `S" lib.4th" EXEC` + `LOG-INFO" Hestia: ready"` + `STARTUP-BANNER`. Not immutable: startup sequence may grow |
### Deleted capsules (historical)
@@ -458,6 +432,31 @@ generalized VM fleet physics mechanism (`capsule_vm_physics.c`/`.h`,
and `docs/working/archive/session-logs/2026-07-05-worklog.md`. Their
former ranges are now UNASSIGNED (see Namespace Map).
`common/msg.4th` (formerly 4055) and `process.4th` (formerly 4300–4301)
were deleted 2026-09-19 (FABRIC-3.6.md tasks 0.2/0.3, part of the
Tripod/kernel reshuffle's Category A surgical strip — see FABRIC-3.5.md
§XXII). Both were confirmed dead by all three of §XXII.2's reachability
routes: zero `EXEC` sites in any boot-loaded capsule, and zero callers
anywhere of their exported words (`HERMES-ACK`/`HERMES-NACK` for the
former; `SPAWN`/`PAUSE`/`RESUME`/`KILL-VM` for the latter).
`common/msg.4th`'s block-4055 entry had claimed it was an immutable ABI
"every messaging VM loads at birth" — `FABRIC-2.md:2773` had already
flagged that claim as stale before this correction. `process.4th`'s
removal also took `messaging.4th`'s `EVENT-EMIT`/`EVENT-WAIT`/
`EVENT-DRAIN` (former block 5030) with it, its only live caller. Their
former ranges are now UNASSIGNED (see Namespace Map).
`hermes/init.4th` (formerly 4100–4109, 4114–4121, 4142–4153) and
`common/messaging.4th` were deleted 2026-09-22 (FABRIC-3.6.md Phase 4 Stage E,
tasks 4.1/4.3), the final step of the Tripod/kernel reshuffle — the Hermes VM
itself and its FORTH messaging layer, superseded by kernel-Hermes
(`src/starkernel/vm/kernel_hermes.c`) after every live message type was cut
over in Phase 3 (see the `hermes/init.4th` entry above for the full account).
Their former ranges are now UNASSIGNED (see Namespace Map). This is the same
"cut over first, delete after, one commit per Category B item, verified on all
three architectures" discipline `compudynamics.4th`/`fleet-k.4th` and
`common/msg.4th`/`process.4th` were deleted under above.
---
## Unassigned Ranges
@@ -476,14 +475,16 @@ former ranges are now UNASSIGNED (see Namespace Map).
| 4008–4009 | UNASSIGNED | ACL extension space |
| 4019–4049 | UNASSIGNED | ACL / zuse extension space |
| 4051–4054 | UNASSIGNED | lib.4th extension space |
| 4056–4059 | UNASSIGNED | common:msg extension space |
| 4055–4059 | UNASSIGNED | Former `common/msg.4th` range (deleted 2026-09-19, FABRIC-3.6.md task 0.2) |
| 4066–4099 | UNASSIGNED | doe-campaign extension space |
| 4154–4199 | UNASSIGNED | Hermes extension space (post-4153) |
| 4100–4153 | UNASSIGNED | Former `hermes/init.4th` range (deleted 2026-09-22, FABRIC-3.6.md Phase 4 Stage E task 4.1). Excludes 4110–4113/4122–4141, which were always Artemis's, hard-locked, unaffected |
| 4154–4199 | UNASSIGNED | Hermes extension space (post-4153), now moot along with 4100–4153 above |
| 4200–4299 | UNASSIGNED | Former compudynamics.4th range (deleted `9323f776`) |
| 4302–4399 | UNASSIGNED | process extension space |
| 4300–4399 | UNASSIGNED | Former `process.4th` range (deleted 2026-09-19, FABRIC-3.6.md task 0.3) |
| 4400–4405 | UNASSIGNED | Former fleet-k.4th range (deleted `9323f776`) |
| 4410–4414, 4420–4424, 4430–4434 | UNASSIGNED | workload-1.4th's private zone, room for growth |
| 4853+ | OPEN | Future capsules — claim here first |
| 4986–4996 | CLAIMED | `hestia/init.4th`, allocated 2026-09-19 (FABRIC-3.6.md task 1.1, Tripod/kernel reshuffle) — file not yet created (task 1.2). Sits between `font.4th`'s last block (4985) and the console proxy's hardcoded `Block 4997` (`capsule_console.c:27-29`) — chosen specifically to avoid 4997, per §XVIII.4 |
| 4853–4985, 4998+ | OPEN | Future capsules — claim here first (excludes 4900–4985, already `fabric.4th`/`font.4th`, and 4986–4996, now claimed above) |
---
@@ -536,3 +537,15 @@ before this doc pass. Not re-verified this pass: whether every other
still-unflagged row elsewhere in this document matches source exactly —
this was a targeted correction of the rows touched by recent work plus
`doe.4th`/`ACL.4th`, not a full line-by-line re-audit of all ~250 blocks.*
*Doc-debt pass, 2026-09-22 — MANIFEST.md updated to ride the Tripod/kernel
reshuffle's Category B strip (FABRIC-3.6.md Phase 4 Stage E, task 5.2 close-out
sweep, §XXII.5). `hermes/init.4th`'s block-by-block table replaced with a
deletion note (git history retains the detail); `common/messaging.4th`'s
deletion recorded in "Deleted capsules"; `init.4th` block 2049,
`doe-campaign.4th` blocks 4060–4062, and `hestia/init.4th` block 4987 updated
to match the post-strip live files (verified by direct read, not by this
document's own prior claims, same discipline as every other pass here).
Former Hermes range (4100–4153, excluding Artemis's hard-locked 4110–4113/
4122–4141) marked UNASSIGNED. Not re-verified this pass: the ~230 other rows
this strip didn't touch.*
+7 -11
View File
@@ -492,13 +492,9 @@ Block 4853
CD-INIT
S" lib.4th" EXEC
Block 4854
( Phase C 2026-08-28: own messaging vocab + subscribe into )
( Hermes's canonical COMMON-CH (idx 2 = Artemis, VM-NAMES- )
( INIT). Hermes always exists by Artemis's birth. )
S" common:messaging.4th" EXEC
MSG-CD-INIT
2 MY-CH-ID !
S" 2 COMMON-CH @ CH-ADD-MBR" S" Hermes" VM-EXEC
( FABRIC-3.6.md Phase 4, Stage E: common:messaging.4th/ )
( MSG-CD-INIT/MY-CH-ID/COMMON-CH subscription all removed )
( -- Hermes, the channel, and the words are all gone. )
STARTUP-BANNER
Block 4856
@@ -525,8 +521,9 @@ Block 4857
Block 4858
( HERA-BLK-ATTACH-REQ ( dev-addr -- ): VM-EXEC'd by MSG- )
( DELIVER when Hera's request arrives. Runs BLK-ATTACH on )
( Artemis's own dictionary (real ACL gating, same as any )
( other message payload), then replies to Hera (idx 0). )
( Artemis's own dictionary, then replies to Hera via kernel- )
( Hermes's KH-BLK-ATTACH-SEND (repl.c) -- FABRIC-3.6.md task )
( 3.8 Stage C, FORTH Hermes no longer sees this message type. )
: HERA-BLK-ATTACH-REQ ( dev-addr -- )
0 ATTACH-ACK-LEN !
DUP ACK-APPEND-NUM
@@ -535,8 +532,7 @@ Block 4858
DROP
BL ACK-APPEND-CHAR
S" BLK-ATTACH-ACK" ACK-APPEND
BLK-ATTACH-EVENT 2 0 ATTACH-ACK-BUF
ATTACH-ACK-LEN @ 0 MSG-SEND ;
ATTACH-ACK-BUF ATTACH-ACK-LEN @ KH-BLK-ATTACH-SEND DROP ;
Block 4860
( LOG-APPEND ( level ts msg-addr msg-u -- ) )
-504
View File
@@ -1,504 +0,0 @@
Block 5003
( common:messaging.4th -- generic per-VM messaging vocab. )
1 CONSTANT SPAWN-EVENT
2 CONSTANT PAUSE-EVENT
3 CONSTANT RESUME-EVENT
4 CONSTANT KILL-EVENT
0 CONSTANT CH-NEGOTIATING
1 CONSTANT CH-OPEN
2 CONSTANT CH-CLOSING
9 CONSTANT MSG-CELLS
6 CONSTANT CH-CELLS
2 CONSTANT MBR-CELLS
32 CONSTANT MSG-MAX
16 CONSTANT CH-MAX
64 CONSTANT MBR-MAX
65208 CONSTANT Q-DECAY
255 CONSTANT MSG-DELIVERED
Block 5038
( CONSOLE-CMD-EVENT: console-VM -> paired user-VM command )
( relay message type, async MSG-SEND/MSG-DELIVER. )
7 CONSTANT CONSOLE-CMD-EVENT
( ELEVATE-REQUEST: H.5/H.8 -- word-ACL elevation ask, )
( carried to Zuse via CH-REQUEST. See SEND-ELEVATE- )
( REQUEST/ELEVATE-GRANT below and in zuse-eligibility.4th. )
8 CONSTANT ELEVATE-REQUEST
Block 5041
( BLK-ATTACH-EVENT: Hera -> Artemis storage-attach request, )
( and Artemis -> Hera ack. Payload is FORTH text, executed )
( by the receiver, same as every other message here -- see )
( HERA-BLK-ATTACH-REQ (artemis:init.4th) and BLK-ATTACH-ACK )
( (repl.c). Messaging-migration decision, Bob, 2026-09-07: )
( Hera keeps polling/sig-checking; the storage-registration )
( step (blk_subsys_attach_device) moves to a real message )
( instead of a direct C call, so identity birth only )
( proceeds once Artemis confirms storage succeeded. )
9 CONSTANT BLK-ATTACH-EVENT
Block 5004
( StadiumBehaviour tags, match stadium.h's enum )
0 CONSTANT SB-MIGRATE
1 CONSTANT SB-DELIVER
2 CONSTANT SB-EXPIRE
3 CONSTANT SB-COOL
-1 CONSTANT STADIUM-NONE
( per-item admission heat for MSG-SEND/CH-ACCEPT. )
( Remaining reservoir after COMMON-CH's Q.1/3 floor, split )
( evenly across MSG-MAX messages + non-COMMON CH-MAX-1 slots. )
Q.1 Q.1 3 / - MSG-MAX CH-MAX 1- + / CONSTANT Q.SLOT
Block 5005
( arenas. heat/capacity via Stadium; MBR keeps its own )
( free list (not part of heat economy). )
CREATE MSG-ARENA MSG-MAX MSG-CELLS * CELLS ALLOT
CREATE CH-ARENA CH-MAX CH-CELLS * CELLS ALLOT
CREATE MBR-ARENA MBR-MAX MBR-CELLS * CELLS ALLOT
VARIABLE MSG-ALLOC-SLOT
VARIABLE CH-ALLOC-SLOT
VARIABLE MBR-FREE-HEAD
VARIABLE MSG-SEQ
VARIABLE CH-ACTIVE
VARIABLE COMMON-CH
VARIABLE MY-CH-ID
Block 5006
( VM routing table. SXIX: 16 slots, not 8 -- see FABRIC-3.md. )
( Hera/Hermes/Artemis stay 0/1/2 (artemis:init.4th depends on )
( it); identities get NEW slots 3-10, never renumbered. )
16 CONSTANT VM-MAX
CREATE VM-NAME-ADDRS VM-MAX CELLS ALLOT
CREATE VM-NAME-LENS VM-MAX CELLS ALLOT
: VM-NAME-REG ( addr u idx -- )
DUP VM-MAX >= IF DROP 2DROP EXIT THEN
>R R@ CELLS VM-NAME-LENS + !
R> CELLS VM-NAME-ADDRS + ! ;
Block 5042
( doe-idx (std79-doe.fth ID-NAME) -> msg-idx: 1..8 -> 3..10. )
: DOE-IDX>MSG-IDX ( doe-idx -- msg-idx ) 2 + ;
: VM-NAMES-INIT ( -- )
S" Hera" 0 VM-NAME-REG
S" Hermes" 1 VM-NAME-REG
S" Artemis" 2 VM-NAME-REG
S" rajames" 1 DOE-IDX>MSG-IDX VM-NAME-REG
S" 00" 2 DOE-IDX>MSG-IDX VM-NAME-REG
S" 01" 3 DOE-IDX>MSG-IDX VM-NAME-REG
S" 02" 4 DOE-IDX>MSG-IDX VM-NAME-REG
S" 03" 5 DOE-IDX>MSG-IDX VM-NAME-REG
S" 04" 6 DOE-IDX>MSG-IDX VM-NAME-REG
S" 05" 7 DOE-IDX>MSG-IDX VM-NAME-REG
S" 06" 8 DOE-IDX>MSG-IDX VM-NAME-REG ;
Block 5007
( INIT-FREE. stamps STADIUM-NONE into each slot's stadium- )
( cell field (msg off 5, ch off 3). Raw offsets: accessors )
( aren't defined yet in file order. )
: MSG-INIT-FREE ( -- )
MSG-MAX 0 DO
STADIUM-NONE I MSG-CELLS * CELLS MSG-ARENA + 5 CELLS + !
LOOP ;
: CH-INIT-FREE ( -- )
CH-MAX 0 DO
STADIUM-NONE I CH-CELLS * CELLS CH-ARENA + 3 CELLS + !
LOOP ;
Block 5008
( MBR-INIT-FREE (member free list, untouched) )
: MBR-INIT-FREE ( -- )
MBR-MAX 1- 0 DO
I MBR-CELLS * CELLS MBR-ARENA +
I 1+ MBR-CELLS * CELLS MBR-ARENA + SWAP !
LOOP
0 MBR-MAX 1- MBR-CELLS * CELLS MBR-ARENA + !
MBR-ARENA MBR-FREE-HEAD ! ;
Block 5009
( MSG-ALLOC: finds a free slot (TYPE=0) first, admits into )
( Stadium only once confirmed free (no leak). )
: MSG-FIND-FREE-SLOT ( -- addr|0 )
MSG-ARENA MSG-MAX 0 DO
DUP @ 0= IF UNLOOP EXIT THEN
MSG-CELLS CELLS +
LOOP DROP 0 ;
Block 5010
( MSG-ALLOC: pulls heat from reservoir first, admits )
( (identity=idx, heat=pulled), rolls back on refusal. )
: MSG-ALLOC ( heat -- addr|0 )
MSG-FIND-FREE-SLOT DUP 0= IF SWAP DROP EXIT THEN
MSG-ALLOC-SLOT !
STADIUM-RES-PULL
MSG-ALLOC-SLOT @ MSG-ARENA - MSG-CELLS CELLS /
SWAP DUP >R
SB-DELIVER STADIUM-ADMIT
DUP STADIUM-NONE = IF
DROP R> STADIUM-RES-PUSH 0 EXIT
THEN
R> DROP
MSG-ALLOC-SLOT @ MSG-CELLS CELLS 0 FILL
MSG-ALLOC-SLOT @ 5 CELLS + !
MSG-ALLOC-SLOT @ ;
Block 5011
( MSG-FREE-NODE: evict from Stadium, clear field )
: MSG-FREE-NODE ( addr -- )
DUP 5 CELLS + @ STADIUM-EVICT DROP
DUP 5 CELLS + STADIUM-NONE SWAP !
DROP ;
Block 5012
( MBR alloc/free (unchanged; not heat economy) )
: MBR-ALLOC ( -- addr|0 )
MBR-FREE-HEAD @ DUP 0= IF EXIT THEN
DUP @ MBR-FREE-HEAD !
DUP MBR-CELLS CELLS 0 FILL ;
: MBR-FREE-NODE ( addr -- )
MBR-FREE-HEAD @ OVER ! MBR-FREE-HEAD ! ;
Block 5013
( CH-FIND-FREE-SLOT. No TYPE field, so freeness is )
( stadium-cell = STADIUM-NONE. CH-ALLOC -> block 4176. )
: CH-FIND-FREE-SLOT ( -- addr|0 )
CH-ARENA CH-MAX 0 DO
DUP 3 CELLS + @ STADIUM-NONE = IF UNLOOP EXIT THEN
CH-CELLS CELLS +
LOOP DROP 0 ;
Block 5014
( CH-ALLOC: pulls heat from reservoir first, admits )
( (identity=idx, heat=pulled), rolls back on refusal. )
: CH-ALLOC ( heat -- addr|0 )
CH-FIND-FREE-SLOT DUP 0= IF SWAP DROP EXIT THEN
CH-ALLOC-SLOT !
STADIUM-RES-PULL
CH-ALLOC-SLOT @ CH-ARENA - CH-CELLS CELLS /
SWAP DUP >R
SB-COOL STADIUM-ADMIT
DUP STADIUM-NONE = IF
DROP R> STADIUM-RES-PUSH 0 EXIT
THEN
R> DROP
CH-ALLOC-SLOT @ CH-CELLS CELLS 0 FILL
CH-ALLOC-SLOT @ 3 CELLS + !
CH-ALLOC-SLOT @ ;
Block 5015
( CH-FREE-NODE: evict from Stadium, clear field )
: CH-FREE-NODE ( addr -- )
DUP 3 CELLS + @ STADIUM-EVICT DROP
DUP 3 CELLS + STADIUM-NONE SWAP !
DROP ;
Block 5016
( message field accessors )
: MSG-TYPE@ ( m -- n ) @ ;
: MSG-TYPE! ( n m -- ) ! ;
: MSG-FROM@ ( m -- n ) 1 CELLS + @ ;
: MSG-FROM! ( n m -- ) 1 CELLS + ! ;
: MSG-TO@ ( m -- n ) 2 CELLS + @ ;
: MSG-TO! ( n m -- ) 2 CELLS + ! ;
: MSG-PADDR@ ( m -- a ) 3 CELLS + @ ;
: MSG-PADDR! ( a m -- ) 3 CELLS + ! ;
: MSG-PLEN@ ( m -- u ) 4 CELLS + @ ;
: MSG-PLEN! ( u m -- ) 4 CELLS + ! ;
( offset 5 = Stadium cell idx. MSG-HEAT@/! -> 4154. )
: MSG-STADIUM-CELL@ ( m -- cell ) 5 CELLS + @ ;
: MSG-STADIUM-CELL! ( cell m -- ) 5 CELLS + ! ;
: MSG-SEQ@ ( m -- n ) 6 CELLS + @ ;
: MSG-SEQ! ( n m -- ) 6 CELLS + ! ;
Block 5017
( message accessors: CH@/CH! )
: MSG-CH@ ( m -- c ) 7 CELLS + @ ;
: MSG-CH! ( c m -- ) 7 CELLS + ! ;
: MSG-ORIG-TYPE@ ( m -- n ) 8 CELLS + @ ;
: MSG-ORIG-TYPE! ( n m -- ) 8 CELLS + ! ;
253 CONSTANT MSG-NACKED
Block 5018
( channel + member accessors )
: CH-ID@ ( c -- n ) @ ;
: CH-ID! ( n c -- ) ! ;
: CH-OWNER@ ( c -- n ) 1 CELLS + @ ;
: CH-OWNER! ( n c -- ) 1 CELLS + ! ;
: CH-STATE@ ( c -- n ) 2 CELLS + @ ;
: CH-STATE! ( n c -- ) 2 CELLS + ! ;
( offset 3 = Stadium cell index. CH-HEAT@/! -> 4154. )
: CH-STADIUM-CELL@ ( c -- cell ) 3 CELLS + @ ;
: CH-STADIUM-CELL! ( cell c -- ) 3 CELLS + ! ;
: CH-MBRS@ ( c -- a ) 4 CELLS + @ ;
: CH-MBRS! ( a c -- ) 4 CELLS + ! ;
: CH-NEXT@ ( c -- a ) 5 CELLS + @ ;
: CH-NEXT! ( a c -- ) 5 CELLS + ! ;
: MBR-NEXT@ ( m -- a ) @ ;
: MBR-VM@ ( m -- n ) 1 CELLS + @ ;
Block 5019
( composed MSG/CH-HEAT@/!, same names/stacks, bodies )
( routed via Stadium. Callers need no changes. )
: MSG-HEAT@ ( m -- q ) MSG-STADIUM-CELL@ STADIUM-HEAT@ ;
: MSG-HEAT! ( q m -- ) MSG-STADIUM-CELL@ STADIUM-HEAT! ;
: CH-HEAT@ ( c -- q ) CH-STADIUM-CELL@ STADIUM-HEAT@ ;
: CH-HEAT! ( q c -- ) CH-STADIUM-CELL@ STADIUM-HEAT! ;
Block 5020
( deliver )
VARIABLE MSG-LAST-MSG
VARIABLE MSG-SEND-TO-IDX
: IDX>NAME ( idx -- addr u )
DUP VM-MAX >= IF DROP 0 0 EXIT THEN
DUP CELLS VM-NAME-ADDRS + @
SWAP CELLS VM-NAME-LENS + @ ;
: MSG-DELIVER ( m -- )
DUP MSG-LAST-MSG !
DUP MSG-PADDR@ OVER MSG-PLEN@
ROT MSG-TO@ IDX>NAME VM-EXEC ;
Block 5021
( MSG-SEND. heat -> MSG-ALLOC's admission directly )
( (zero-heat would lose eviction-fallback density )
( comparisons), not set afterward as before. )
: MSG-SEND ( type from to paddr plen ch -- )
3 PICK MSG-SEND-TO-IDX !
Q.SLOT MSG-ALLOC DUP 0= IF 2DROP 2DROP 2DROP DROP EXIT THEN
>R
MSG-SEQ @ 1+ DUP MSG-SEQ ! R@ MSG-SEQ!
R@ MSG-CH!
R@ MSG-PLEN! R@ MSG-PADDR!
R@ MSG-TO! R@ MSG-FROM! DUP R@ MSG-TYPE! R@ MSG-ORIG-TYPE!
R> DROP
MSG-SEND-TO-IDX @ IDX>NAME SWITCH-MARK-WORK ;
Block 5022
( MSG-COOL-ONE/ALL: linear decay per tick )
VARIABLE MSG-SCAN
: MSG-COOL-ONE ( m -- )
DUP MSG-HEAT@ Q-DECAY Q.* SWAP MSG-HEAT! ;
: MSG-COOL-ALL ( -- )
MSG-ARENA MSG-SCAN !
MSG-MAX 0 DO
MSG-SCAN @ MSG-HEAT@ 0 > IF
MSG-SCAN @ MSG-COOL-ONE
THEN
MSG-SCAN @ MSG-CELLS CELLS + MSG-SCAN !
LOOP ;
Block 5023
( MSG-TOTAL-HEAT )
: MSG-TOTAL-HEAT ( -- q48 )
0 MSG-ARENA MSG-SCAN !
MSG-MAX 0 DO
MSG-SCAN @ MSG-TYPE@ 0 <> IF
MSG-SCAN @ MSG-HEAT@ +
THEN
MSG-SCAN @ MSG-CELLS CELLS + MSG-SCAN !
LOOP ;
: MSG-REDELIVER-NACKED ( -- )
MSG-ARENA MSG-SCAN !
MSG-MAX 0 DO
MSG-SCAN @ MSG-TYPE@ MSG-NACKED = IF
MSG-SCAN @ MSG-ORIG-TYPE@ MSG-SCAN @ MSG-TYPE! THEN
MSG-SCAN @ MSG-CELLS CELLS + MSG-SCAN !
LOOP ;
Block 5024
( MSG-DELIVER-ALL )
: MSG-DELIVER-ALL ( -- )
MSG-ARENA MSG-SCAN !
MSG-MAX 0 DO
MSG-SCAN @ MSG-TYPE@ 0 <>
MSG-SCAN @ MSG-TYPE@ MSG-DELIVERED <> AND
MSG-SCAN @ MSG-TYPE@ MSG-NACKED <> AND IF
MSG-SCAN @ MSG-DELIVER
MSG-SCAN @ MSG-TYPE@ 0 <> IF
MSG-DELIVERED MSG-SCAN @ MSG-TYPE!
THEN
THEN
MSG-SCAN @ MSG-CELLS CELLS + MSG-SCAN !
LOOP ;
Block 5025
( message reaping )
( K reap only fires at heat=0: freed K contribution is 0. )
( Force-reap not yet implemented. If added: explicit )
( K redistribution will be required here. )
: MSG-REAP ( -- )
MSG-ARENA MSG-SCAN !
MSG-MAX 0 DO
MSG-SCAN @ MSG-HEAT@ 0 = IF
MSG-SCAN @ MSG-TYPE@ 0 <> IF
0 MSG-SCAN @ MSG-TYPE!
MSG-SCAN @ MSG-FREE-NODE
THEN
THEN
MSG-SCAN @ MSG-CELLS CELLS + MSG-SCAN !
LOOP ;
Block 5026
( channel cooling + heat aggregate )
VARIABLE CH-SCAN
: CH-COOL-ALL ( -- )
CH-ACTIVE @ CH-SCAN !
BEGIN CH-SCAN @ 0 <> WHILE
CH-SCAN @ CH-HEAT@ Q-DECAY Q.* CH-SCAN @ CH-HEAT!
CH-SCAN @ CH-NEXT@ CH-SCAN !
REPEAT ;
: CH-TOTAL-HEAT ( -- q48 )
0 CH-ACTIVE @ CH-SCAN !
BEGIN CH-SCAN @ 0 <> WHILE
CH-SCAN @ CH-HEAT@ +
CH-SCAN @ CH-NEXT@ CH-SCAN !
REPEAT ;
Block 5027
( channel reaping )
: CH-REAP-SAFE ( -- )
CH-ACTIVE @ CH-SCAN !
0 CH-ACTIVE !
BEGIN CH-SCAN @ 0 <> WHILE
CH-SCAN @ CH-NEXT@
CH-SCAN @ CH-HEAT@ 0 =
CH-SCAN @ COMMON-CH @ <> AND IF
CH-SCAN @ CH-FREE-NODE
ELSE
CH-ACTIVE @ CH-SCAN @ CH-NEXT!
CH-SCAN @ CH-ACTIVE !
THEN
CH-SCAN !
REPEAT ;
Block 5028
( COMMON + MSG-TICK (was HERMES-TICK -- generic now). )
( floor=Q.1/3. COMMON-CH VARIABLE is in block 4101. )
: COMMON-INIT ( -- )
Q.1 3 / CH-ALLOC DUP COMMON-CH !
0 OVER CH-ID! 0 OVER CH-OWNER!
CH-OPEN OVER CH-STATE!
0 OVER CH-MBRS!
CH-ACTIVE @ OVER CH-NEXT!
CH-ACTIVE ! ;
( floor-refresh is reservoir-constrained, may no-op under )
( pressure. )
: MSG-TICK ( -- )
MSG-DELIVER-ALL MSG-REDELIVER-NACKED
MSG-COOL-ALL MSG-REAP
CH-COOL-ALL CH-REAP-SAFE
Q.1 3 / COMMON-CH @ CH-HEAT! ;
Block 5029
( MSG-K (was HERMES-K -- generic now). WELCOME stays )
( behind in each VM's own init.4th. )
: MSG-K ( -- q48 )
MSG-TOTAL-HEAT CH-TOTAL-HEAT + STADIUM-RES@ +
STADIUM-WORD-HEAT + ;
Block 5030
( event compat interface. SPAWN/KILL notify-Hera is )
( automatic: see vm_physics_init/retire in mama_word_birth. )
: EVENT-EMIT ( type -- ) DROP ;
: EVENT-WAIT ( -- type )
MSG-ARENA MSG-SCAN !
MSG-MAX 0 DO
MSG-SCAN @ MSG-TYPE@ 0 <> IF
MSG-SCAN @ MSG-TYPE@ UNLOOP EXIT THEN
MSG-SCAN @ MSG-CELLS CELLS + MSG-SCAN !
LOOP 0 ;
: EVENT-DRAIN ( -- )
( no-op: MSG-REAP owns cleanup; drain breaks async ) ;
Block 5031
( channel negotiation: mint/request )
: CH-MINT-ID ( owner -- id )
MSG-SEQ @ 1+ DUP MSG-SEQ !
SWAP 32 LSHIFT OR ;
( H.8: initiator-only gate. Refuses if `from` doesn't )
( match MY-CH-ID -- no posting while claiming another VM. )
: CH-REQUEST ( type from to paddr plen -- )
3 PICK MY-CH-ID @ <> IF 2DROP 2DROP DROP EXIT THEN
COMMON-CH @ CH-STATE@ CH-OPEN = IF
COMMON-CH @ MSG-SEND
ELSE 2DROP 2DROP DROP THEN ;
Block 5032
( channel ops: accept confirm close )
: CH-ACCEPT ( -- ch|0 )
Q.SLOT CH-ALLOC DUP 0= IF EXIT THEN
1 CH-MINT-ID OVER CH-ID!
1 OVER CH-OWNER!
CH-NEGOTIATING OVER CH-STATE!
0 OVER CH-MBRS!
CH-ACTIVE @ OVER CH-NEXT!
DUP CH-ACTIVE ! ;
: CH-CONFIRM ( ch -- )
DUP CH-STATE@ CH-NEGOTIATING = IF CH-OPEN SWAP CH-STATE!
ELSE DROP THEN ;
: CH-CLOSE ( ch -- )
DUP COMMON-CH @ = IF DROP EXIT THEN
CH-CLOSING SWAP CH-STATE! ;
Block 5033
( MSG-CD-INIT (was CD-INIT -- renamed to avoid colliding )
( with Artemis's own unrelated block-subsystem CD-INIT). )
( common:msg.4th's HERMES-ACK/NACK indirection is retired: )
( every VM now has its own local MSG-ACK-LAST/NACK-LAST. )
: MSG-CD-INIT ( -- )
MSG-ARENA MSG-MAX MSG-CELLS * CELLS 0 FILL
CH-ARENA CH-MAX CH-CELLS * CELLS 0 FILL
MBR-ARENA MBR-MAX MBR-CELLS * CELLS 0 FILL
MSG-INIT-FREE
CH-INIT-FREE
MBR-INIT-FREE
0 MSG-SEQ !
0 MSG-LAST-MSG !
0 CH-ACTIVE ! -1 MY-CH-ID !
COMMON-INIT
VM-NAMES-INIT ;
Block 5034
( ACK/NACK -- now purely local, no VM-EXEC indirection. )
: MSG-ACK-LAST ( -- )
MSG-LAST-MSG @ DUP 0= IF DROP EXIT THEN
0 OVER MSG-TYPE! MSG-FREE-NODE ;
: MSG-NACK-LAST ( -- )
MSG-LAST-MSG @ DUP 0= IF DROP EXIT THEN
DUP MSG-HEAT@ 2 / OVER MSG-HEAT!
MSG-NACKED SWAP MSG-TYPE! ;
Block 5035
( MSG-STATUS (was HERMES-STATUS -- generic now) )
: MSG-USED ( -- n )
0 MSG-ARENA MSG-SCAN !
MSG-MAX 0 DO
MSG-SCAN @ MSG-TYPE@ 0 <> IF 1+ THEN
MSG-SCAN @ MSG-CELLS CELLS + MSG-SCAN !
LOOP ;
: CH-USED ( -- n )
0 CH-ACTIVE @ CH-SCAN !
BEGIN CH-SCAN @ 0 <> WHILE
1+
CH-SCAN @ CH-NEXT@ CH-SCAN !
REPEAT ;
: MSG-STATUS ( -- )
MSG-USED . ." msgs " CH-USED . ." channels" CR ;
Block 5036
( member management )
: MBR-NEXT! ( a m -- ) ! ;
: MBR-VM! ( n m -- ) 1 CELLS + ! ;
: CH-ADD-MBR ( vm ch -- )
MBR-ALLOC DUP 0= IF DROP 2DROP EXIT THEN
ROT OVER MBR-VM!
OVER CH-MBRS@ OVER MBR-NEXT!
SWAP CH-MBRS! ;
Block 5037
( Phase 2: real multi-member broadcast )
VARIABLE BC-TYPE VARIABLE BC-FROM
VARIABLE BC-PADDR VARIABLE BC-PLEN
VARIABLE BC-CH VARIABLE BC-SCAN
: MSG-BROADCAST ( type from paddr plen ch -- )
BC-CH ! BC-PLEN ! BC-PADDR ! BC-FROM ! BC-TYPE !
BC-CH @ CH-MBRS@ BC-SCAN !
BEGIN BC-SCAN @ 0<> WHILE
BC-TYPE @ BC-FROM @ BC-SCAN @ MBR-VM@
BC-PADDR @ BC-PLEN @ BC-CH @ MSG-SEND
BC-SCAN @ MBR-NEXT@ BC-SCAN !
REPEAT ;
Block 5039
( H.8: SEND-ELEVATE-REQUEST payload builder -- )
( scratch text buffer + append primitives. )
CREATE ELEVATE-REQ-BUF 256 ALLOT
VARIABLE ELEVATE-REQ-LEN
: ELEVATE-REQ-RESET ( -- ) 0 ELEVATE-REQ-LEN ! ;
: ELEVATE-REQ-PUTC ( c -- )
ELEVATE-REQ-BUF ELEVATE-REQ-LEN @ + C!
1 ELEVATE-REQ-LEN +! ;
: ELEVATE-REQ-APPEND ( addr u -- )
>R
ELEVATE-REQ-BUF ELEVATE-REQ-LEN @ +
R@ CMOVE
ELEVATE-REQ-LEN @ R> + ELEVATE-REQ-LEN ! ;
: ELEVATE-REQ-NUM ( n -- )
<# 0 SWAP #S #> ELEVATE-REQ-APPEND ;
Block 5040
( SEND-ELEVATE-REQUEST: ask Zuse for word-ACL )
( elevation. pk3..pk0 order so top-down emits )
( pk0 first; waddr/wu = target word name text. )
: SEND-ELEVATE-REQUEST ( pk3 pk2 pk1 pk0 waddr wu -- )
ELEVATE-REQ-RESET
83 ELEVATE-REQ-PUTC 34 ELEVATE-REQ-PUTC
32 ELEVATE-REQ-PUTC
ELEVATE-REQ-APPEND
34 ELEVATE-REQ-PUTC 32 ELEVATE-REQ-PUTC
ELEVATE-REQ-NUM 32 ELEVATE-REQ-PUTC
ELEVATE-REQ-NUM 32 ELEVATE-REQ-PUTC
ELEVATE-REQ-NUM 32 ELEVATE-REQ-PUTC
ELEVATE-REQ-NUM 32 ELEVATE-REQ-PUTC
S" ELEVATE-GRANT" ELEVATE-REQ-APPEND
ELEVATE-REQUEST MY-CH-ID @ 0 ELEVATE-REQ-BUF
ELEVATE-REQ-LEN @ CH-REQUEST ;
-10
View File
@@ -1,10 +0,0 @@
Block 4055
( common:msg.4th — Hermes participant interface )
( Load into every messaging VM at birth: )
( S" common:msg.4th" EXEC )
: HERMES-ACK ( -- )
LOG-DEBUG" msg: ACK sent"
S" MSG-ACK-LAST" S" Hermes" VM-EXEC ;
: HERMES-NACK ( -- )
LOG-DEBUG" msg: NACK sent"
S" MSG-NACK-LAST" S" Hermes" VM-EXEC ;
+7 -10
View File
@@ -1,20 +1,17 @@
Block 4060
( doe-campaign.4th: Compudynamics DoE campaign orchestrator )
( Requires: doe.4th (word-level) + real VM fleet touches. )
( Child VMs must define LOAD-DOE. )
: SETUP-HERMES ( -- )
S" Hermes" BIRTH
S" LOAD-DOE" S" Hermes" VM-EXEC ;
( Child VMs must define LOAD-DOE. FABRIC-3.6.md Phase 4, )
( Stage E: SETUP-HERMES removed -- Hermes no longer exists. )
: SETUP-ARTEMIS ( -- )
S" Artemis" BIRTH
S" LOAD-DOE" S" Artemis" VM-EXEC ;
: SETUP-VMS ( -- )
SETUP-HERMES SETUP-ARTEMIS ;
SETUP-ARTEMIS ;
Block 4061
( Phase 1: each VM runs DOE-WORK once for baseline. )
( FABRIC-3.6.md Phase 4, Stage E: Hermes's own line removed. )
: PHASE1-DOE ( -- )
." Phase 1: Hermes" CR
S" DOE-WORK" S" Hermes" VM-EXEC
." Phase 1: Artemis" CR
S" DOE-WORK" S" Artemis" VM-EXEC ;
( HISTORICAL: CD-WORK removed 2026-07-08 -- dead code, )
@@ -24,8 +21,9 @@ Block 4062
( CD-TICK: one real fleet touch pass -- not a fake pump. )
( Heat only moves via real VM-EXEC/VM-CALL/VM-STEP now; )
( there is nothing left to artificially bump per VM. )
( FABRIC-3.6.md Phase 4, Stage E: Hermes's own line )
( removed -- one touch per tick now, not two. )
: CD-TICK ( -- )
S" DOE-WORK" S" Hermes" VM-EXEC
S" DOE-WORK" S" Artemis" VM-EXEC ;
: CD-DOE ( n -- ) 0 DO CD-TICK LOOP ;
: CAMPAIGN-STATUS ( -- )
@@ -47,11 +45,10 @@ Block 4064
( Smoke test: 1 rep seed 1959 + 16 real fleet touches )
( HISTORICAL: VM-HEAT! artificial seeding removed -- new )
( VMs start at zero heat by construction, no seed needed. )
( FABRIC-3.6.md Phase 4, Stage E: Hermes's own rep removed. )
: SMOKE-CAMPAIGN ( -- )
." === Smoke: 1 rep seed 1959 ===" CR
SETUP-VMS
." Hermes 1 rep..." CR
S" 1959 1 EXEC-DOE" S" Hermes" VM-EXEC
." Artemis 1 rep..." CR
S" 1959 1 EXEC-DOE" S" Artemis" VM-EXEC
." 16 real fleet touches..." CR
-23
View File
@@ -1,23 +0,0 @@
Block 4855
( Hermes v1 -- WELCOME. Generic messaging vocab moved to )
( common:messaging.4th, FABRIC-2.md Phase C 2026-08-28. )
: WELCOME ( -- ) LOG-INFO" Hermes: loaded" ;
WELCOME
Block 4153
( LOAD-DOE: pulls in doe.4th's word-level DOE-WORK )
( workload, for doe-campaign.4th's remote VM-EXEC. )
: LOAD-DOE ( -- ) S" doe.4th" EXEC ;
Block 5116
( Hermes owns the one real, canonical COMMON-CH. Every )
( other VM subscribes into it via VM-EXEC (see Artemis's )
( and Hera's own init.4th); Hera always exists first, so )
( Hermes adds her here rather than Hera subscribing to a )
( VM that doesn't exist yet at Hera's own birth. )
S" common:messaging.4th" EXEC
MSG-CD-INIT
1 MY-CH-ID !
S" lib.4th" EXEC
1 COMMON-CH @ CH-ADD-MBR
0 COMMON-CH @ CH-ADD-MBR
LOG-INFO" Hermes: ready"
STARTUP-BANNER
+22
View File
@@ -0,0 +1,22 @@
Block 4986
( Hestia -- WELCOME. Third Tripod leg (Hera/Artemis/Hestia, )
( FABRIC-3.5.md SII/SIV), bind point + drawing fabric owner. )
: WELCOME ( -- ) LOG-INFO" Hestia: loaded" ;
WELCOME
Block 4988
( fabric.4th/font.4th moved here from init.4th (Hera) -- )
( FABRIC-3.6.md tasks 1.6/1.7, merged into one commit: they )
( are not independent -- font.4th's G-LINE/G-ELLIPSE calls )
( are fabric.4th's own words, so moving one without the )
( other breaks whichever VM keeps only half the pair. Order )
( preserved: fabric.4th before font.4th, same as init.4th's )
( own load order before this move. )
S" fabric.4th" EXEC
S" font.4th" EXEC
Block 4987
( FABRIC-3.6.md Phase 4, Stage E: common:messaging.4th/ )
( MSG-CD-INIT/MY-CH-ID/COMMON-CH subscription all removed )
( -- the whole COMMON-CH concept is gone with the file. )
S" lib.4th" EXEC
LOG-INFO" Hestia: ready"
STARTUP-BANNER
+2 -5
View File
@@ -17,11 +17,8 @@ S" ACL.4th" EXEC
S" block-acl.4th" EXEC
S" zuse-eligibility.4th" EXEC
S" lib.4th" EXEC
S" fabric.4th" EXEC
S" font.4th" EXEC
( SXX: same pattern Hermes/Artemis/console/mint already use. )
S" common:messaging.4th" EXEC
MSG-CD-INIT
( FABRIC-3.6.md Phase 4, Stage E: common:messaging.4th/ )
( MSG-CD-INIT removed -- no longer exists, no longer needed. )
Block 2050
( Hera boot — banner )
BOOT-BANNER
-28
View File
@@ -1,28 +0,0 @@
Block 4300
( process.4th — lifecycle phase operators )
( Event codes must match hermes/init.4th )
1 CONSTANT SPAWN-EVENT
2 CONSTANT PAUSE-EVENT
3 CONSTANT RESUME-EVENT
4 CONSTANT KILL-EVENT
( SPAWN: birth VM, notify Hermes )
: SPAWN ( c-addr u -- )
BIRTH
LOG-INFO" process: spawned"
S" 1 EVENT-EMIT" S" Hermes" VM-EXEC ;
( PAUSE: notify Hermes, send STOP to the named VM )
: PAUSE ( c-addr u -- )
S" 2 EVENT-EMIT" S" Hermes" VM-EXEC
S" STOP" 2SWAP VM-EXEC ;
Block 4301
( Lifecycle operators: resume, kill, phase )
( RESUME: notify Hermes, START the named VM )
: RESUME ( c-addr u -- )
S" 3 EVENT-EMIT" S" Hermes" VM-EXEC
LOG-INFO" process: resumed"
START ;
( KILL-VM: notify Hermes, then kill VM )
: KILL-VM ( c-addr u -- )
S" 4 EVENT-EMIT" S" Hermes" VM-EXEC
LOG-INFO" process: killed"
KILL ;
+3 -3
View File
@@ -3,9 +3,9 @@ Block 4021
( C prims: ZUSE-ELIGIBILITY-ADD ZUSE-ELIGIBLE? )
( NAME>XT ELEVATE-PUBKEY-UNPACK )
( Policy word this file: ELEVATE-GRANT. Runs in )
( Hera's own dict -- delivered here by messaging.4th's )
( MSG-DELIVER via VM-EXEC when a session calls the )
( common:messaging.4th SEND-ELEVATE-REQUEST entrypoint. )
( Hera's own dict -- delivered here by kernel-Hermes's own )
( drain checkpoint (FABRIC-3.6.md task 3.10), not FORTH )
( MSG-DELIVER (messaging.4th, removed Phase 4 Stage E). )
( FABRIC-2.md H.12 step 21, 2026-09-03. )
CREATE ELEVATE-PK-BUF 32 ALLOT
+4
View File
@@ -12,6 +12,8 @@ Block 4016
( >BODY-then-store, so a pinned CONSTANT isn't tamper-proof. )
( Read with ZUSE-PUBKEY@ / ZUSE-CERT-INSTALLED? -- both C )
( primitives, read-only; the seed has no FORTH access at all. )
( ZUSE-ELIGIBILITY-ADD denied by default -- see block 4017. )
0 ['] ZUSE-ELIGIBILITY-ADD ACL-ALLOW!
Block 4017
( ACL-ZUSE-BOOT ( -- ) re-invokable: capsule_zuse_boot.c )
@@ -23,6 +25,8 @@ Block 4017
: ACL-ZUSE-BOOT ( -- )
ZUSE-CERT-INSTALLED? IF
ZUSE-AUTHENTICATE
1 ['] ZUSE-ELIGIBILITY-ADD ACL-ALLOW!
['] ZUSE-ELIGIBILITY-ADD ACL-PIN
LOG-INFO" zuse: activated"
ELSE
LOG-INFO" zuse: NOT activated -- no cert installed"
+1 -1
View File
@@ -1,7 +1,7 @@
# configs/
Example Kconfig `defconfig` files — starting points for `make -f
Makefile.starkernel ARCH=<arch> defconfig` (kernel) or the equivalent
kernel/Makefile ARCH=<arch> defconfig` (kernel) or the equivalent
hosted target, matching the current committed default build behavior for
each profile. Loaded via `tools/kconfig/conf`.
View File
View File
+787
View File
@@ -0,0 +1,787 @@
# StarForth Primitive Word Reference
This reference covers every **C-implemented primitive** word that StarForth registers. It was built from
`admin/LithosAnanake` at commit `6302dcb` (2026-09-23). It lists only words registered in C
through `register_word()` or `vm_create_word()`. Words defined in FORTH inside capsules (`*.4th`) are out of scope.
Sources:
- `src/word_registry.c`: `register_forth79_words()` registers the core set in every VM.
- `src/word_source/*.c`: one file per module.
- `src/starkernel/capsule/mama_forth_words.c`: kernel-only Hera (Mama) and child-VM words.
- `src/starkernel/repl.c` and `src/starkernel/doe_log.c`: kernel-only REPL and DoE words.
---
## Conventions
| Item | Meaning |
|---|---|
| Cell | `cell_t` is `int64_t`, so every cell is 64 bits and signed. |
| Flag | TRUE is `-1` (all bits set) and FALSE is `0`. Words that take a flag treat any non-zero value as true. |
| `addr` | A **VM address**: a byte offset into the VM's 5 MB linear memory (`VM_MEMORY_SIZE`), not a host pointer. |
| `c-addr u` | A string given as its address and length. |
| `d`, `ud` | A double-cell number made of two cells, with the **high cell on top**. |
| `xt` | An execution token. In StarForth this is the `DictEntry*` of the word. |
| `q` | A Q48.16 fixed-point value in one cell (`1.0` = `65536`). |
| `"name"` | The word parses a name from the input stream after it. |
| `( R: ... )` | The effect on the return stack. |
| **IMM** | The word is IMMEDIATE, so it runs even while compiling. |
| **CO** | The word is compile-only and sets `vm->error` if used outside a definition. |
| **K** | The word is registered only in the kernel build (`__STARKERNEL__`). |
| **H** | The word is registered only in the hosted build (the Linux or macOS binary). |
Errors: a primitive does not throw. On stack underflow or overflow, a bad address, or division by zero it sets
`vm->error = 1` and logs a message.
Stack limits: the data stack and return stack hold 1024 cells each (`STACK_SIZE`). A word name can be at most 31
characters (`WORD_NAME_MAX`).
Shadowing: when a later module registers a name again, the newer entry wins lookups. For example, `MOD`, `/MOD`,
`*/` and `*/MOD` are registered by the arithmetic module and again by the mixed-arithmetic module, so the
mixed-arithmetic versions are the active ones.
---
## Contents
1. [Stack](#1-stack)
2. [Return stack](#2-return-stack)
3. [Memory](#3-memory)
4. [Arithmetic](#4-arithmetic)
5. [Logic and comparison](#5-logic-and-comparison)
6. [Mixed-precision arithmetic](#6-mixed-precision-arithmetic)
7. [Double-cell numbers](#7-double-cell-numbers)
8. [Number formatting and output](#8-number-formatting-and-output)
9. [Strings, parsing, and input](#9-strings-parsing-and-input)
10. [Terminal I/O](#10-terminal-io)
11. [Blocks and mass storage](#11-blocks-and-mass-storage)
12. [Dictionary space](#12-dictionary-space)
13. [Dictionary manipulation](#13-dictionary-manipulation)
14. [Vocabularies](#14-vocabularies)
15. [System](#15-system)
16. [Line editor](#16-line-editor)
17. [Defining words and the compiler](#17-defining-words-and-the-compiler)
18. [Control flow](#18-control-flow)
19. [StarForth extensions](#19-starforth-extensions)
20. [Word-level ACL](#20-word-level-acl)
21. [Physics: benchmark and diagnostics](#21-physics-benchmark-and-diagnostics)
22. [Physics: pipelining diagnostics](#22-physics-pipelining-diagnostics)
23. [Physics: freeze, heat, and decay](#23-physics-freeze-heat-and-decay)
24. [Dictionary heat optimisation](#24-dictionary-heat-optimisation)
25. [Logging](#25-logging)
26. [Q48.16 fixed-point math](#26-q4816-fixed-point-math)
27. [Inference engine (SSM, L8, and Bayes)](#27-inference-engine-ssm-l8-and-bayes)
28. [DEFER and IS](#28-defer-and-is)
29. [Framebuffer (Hestia only)](#29-framebuffer-hestia-only)
30. [Keyboard](#30-keyboard)
31. [TrueType text](#31-truetype-text)
32. [REPL scrollback](#32-repl-scrollback)
33. [Kernel REPL and DoE hooks](#33-kernel-repl-and-doe-hooks)
34. [Hera (Mama) and child-VM words](#34-hera-mama-and-child-vm-words)
35. [Hosted lifecycle stubs](#35-hosted-lifecycle-stubs)
36. [Implementation quirks to know](#36-implementation-quirks-to-know)
---
## 1. Stack
`src/word_source/stack_words.c`
| Word | Stack | Description |
|---|---|---|
| `DROP` | `( x -- )` | Discards the top cell. |
| `DUP` | `( x -- x x )` | Copies the top cell. |
| `?DUP` | `( x -- x x \| 0 -- 0 )` | Copies the top cell only when it is non-zero. The usual idiom is `?DUP IF ... THEN`. |
| `SWAP` | `( x1 x2 -- x2 x1 )` | Swaps the top two cells. |
| `OVER` | `( x1 x2 -- x1 x2 x1 )` | Copies the second cell to the top. |
| `ROT` | `( x1 x2 x3 -- x2 x3 x1 )` | Moves the third cell to the top. |
| `-ROT` | `( x1 x2 x3 -- x3 x1 x2 )` | Moves the top cell down to third place. This is the reverse of `ROT`. |
| `DEPTH` | `( -- n )` | Pushes the number of cells that were on the data stack before `DEPTH` ran. |
| `PICK` | `( xn … x0 n -- xn … x0 xn )` | Copies the n-th cell to the top. **0-based:** `0 PICK` is `DUP` and `1 PICK` is `OVER`. An error occurs if `n < 0` or `n ≥ depth`. |
| `ROLL` | `( … n -- … )` | Moves a cell to the top and closes the gap. **Non-standard:** `n` counts from the *bottom* of the stack (1-based), so `1 ROLL` moves the deepest cell to the top. `0 ROLL` does nothing. See [§36](#36-implementation-quirks-to-know). |
## 2. Return stack
`src/word_source/return_stack_words.c`
| Word | Stack | Description |
|---|---|---|
| `>R` | `( x -- ) ( R: -- x )` | Moves a cell from the data stack to the return stack. Inside a definition, balance it with `R>` before `;` or `EXIT`. |
| `R>` | `( -- x ) ( R: x -- )` | Moves a cell from the return stack back to the data stack. |
| `R@` | `( -- x ) ( R: x -- x )` | Copies the top of the return stack without removing it. |
## 3. Memory
`src/word_source/memory_words.c`. Every address is a VM byte offset and is checked against the VM's memory bounds.
| Word | Stack | Description |
|---|---|---|
| `@` | `( addr -- x )` | Fetches the cell at `addr`. |
| `!` | `( x addr -- )` | Stores `x` at `addr`. |
| `C@` | `( addr -- c )` | Fetches the byte at `addr`, zero-extended. |
| `C!` | `( c addr -- )` | Stores the low 8 bits of `c` at `addr`. |
| `+!` | `( n addr -- )` | Adds `n` to the cell at `addr`. |
| `-!` | `( n addr -- )` | Subtracts `n` from the cell at `addr`. |
| `2@` | `( addr -- x-lo x-hi )` | Fetches two cells: the low cell from `addr` and the high cell from `addr+8`, leaving the high cell on top. |
| `2!` | `( x-lo x-hi addr -- )` | Stores two cells: the low cell at `addr` and the high cell at `addr+8`. |
| `FILL` | `( addr u c -- )` | Fills `u` bytes starting at `addr` with the byte `c`. |
| `MOVE` | `( src dst u -- )` | Copies `u` bytes from `src` to `dst`. It is safe when the ranges overlap because it uses memmove semantics. |
| `ERASE` | `( addr u -- )` | Sets `u` bytes to zero. |
| `CELLS` | `( n -- n*8 )` | Scales a cell count to a byte count. |
## 4. Arithmetic
`src/word_source/arithmetic_words.c`. All arithmetic is signed 64-bit, and division truncates toward zero as in C.
| Word | Stack | Description |
|---|---|---|
| `+` | `( n1 n2 -- n1+n2 )` | Adds the two cells. |
| `-` | `( n1 n2 -- n1-n2 )` | Subtracts `n2` from `n1`. |
| `*` | `( n1 n2 -- n1*n2 )` | Multiplies the two cells. The result wraps modulo 2⁶⁴. |
| `/` | `( n1 n2 -- n1/n2 )` | Divides, truncating toward zero. Division by zero sets an error. |
| `MOD` | `( n1 n2 -- rem )` | Pushes the remainder of `n1 / n2`, which has the sign of `n1`. This name is shadowed by §6. |
| `/MOD` | `( n1 n2 -- rem quot )` | Pushes the remainder and the quotient, with the quotient on top. This name is shadowed by §6. |
| `*/` | `( n1 n2 n3 -- n1*n2/n3 )` | Multiplies and then divides using a wide intermediate. This name is shadowed by §6. |
| `*/MOD` | `( n1 n2 n3 -- rem quot )` | Like `*/`, but also leaves the remainder. This name is shadowed by §6. |
| `1+` `1-` | `( n -- n±1 )` | Increments or decrements by 1. |
| `2+` `2-` | `( n -- n±2 )` | Adds or subtracts 2. |
| `2*` | `( n -- n*2 )` | Shifts left by one bit. |
| `2/` | `( n -- n/2 )` | Shifts right by one bit, keeping the sign (arithmetic shift). |
| `ABS` | `( n -- \|n\| )` | Pushes the absolute value. |
| `NEGATE` | `( n -- -n )` | Pushes the two's-complement negation. |
| `MIN` `MAX` | `( n1 n2 -- n3 )` | Pushes the smaller or the larger value, compared as signed numbers. |
## 5. Logic and comparison
`src/word_source/logical_words.c`. Comparison words return a proper flag of `-1` or `0`.
| Word | Stack | Description |
|---|---|---|
| `AND` `OR` `XOR` | `( x1 x2 -- x3 )` | Bitwise AND, OR, and XOR. |
| `NOT` | `( x -- flag )` | **FORTH-79 logical NOT:** `0` gives `TRUE` and any other value gives `FALSE`. This is *not* a bitwise complement; use `INVERT` for that. |
| `INVERT` | `( x -- ~x )` | Bitwise complement (from FORTH-83). |
| `LSHIFT` | `( x u -- x<<u )` | Logical shift left by `u` bits. |
| `RSHIFT` | `( x u -- x>>u )` | Logical (unsigned) shift right by `u` bits. |
| `0=` | `( n -- flag )` | True if `n` is 0. |
| `0<` | `( n -- flag )` | True if `n` is negative. |
| `0>` | `( n -- flag )` | True if `n` is positive. |
| `0<>` | `( n -- flag )` | True if `n` is not 0. |
| `=` `<>` | `( n1 n2 -- flag )` | Tests for equality or inequality. |
| `<` `>` `<=` `>=` | `( n1 n2 -- flag )` | Signed comparisons of `n1` against `n2`. |
| `U<` `U>` | `( u1 u2 -- flag )` | Unsigned comparisons. |
| `WITHIN` | `( n lo hi -- flag )` | True if `lo ≤ n < hi`, using the standard half-open range. |
| `TRUE` | `( -- -1 )` | Pushes the canonical true flag. |
| `FALSE` | `( -- 0 )` | Pushes the canonical false flag. |
## 6. Mixed-precision arithmetic
`src/word_source/mixed_arithmetic_words.c`. A double here means two full 64-bit cells (128 bits).
| Word | Stack | Description |
|---|---|---|
| `M+` | `( d n -- d' )` | Adds a signed single to a double and propagates the carry into the high cell. |
| `M-` | `( d n -- d' )` | Subtracts a signed single from a double and propagates the borrow. |
| `M*` | `( n1 n2 -- d )` | Multiplies 64×64 into a full 128-bit signed product, using `__int128` internally. The result can be printed with `D.`. |
| `M/MOD` | `( d n -- rem quot )` | Divides a 128-bit double by a single, leaving the quotient on top. It uses bit-serial long division, so it works in the freestanding kernel without libgcc. |
| `MOD` | `( n1 n2 -- rem )` | **Active version.** Computes `n1 % n2`. Division by zero sets an error. |
| `/MOD` | `( n1 n2 -- rem quot )` | **Active version.** Leaves the remainder under the quotient. Division by zero sets an error. |
| `*/` | `( n1 n2 n3 -- n4 )` | **Active version.** Computes `(n1*n2)/n3` with a wide intermediate, so `n1*n2` does not overflow. Division by zero sets an error. Typical use is scaling, for example `x 355 113 */`. |
| `*/MOD` | `( n1 n2 n3 -- rem quot )` | **Active version.** Like `*/`, but also leaves the remainder under the quotient. |
## 7. Double-cell numbers
`src/word_source/double_words.c`. In every stack picture, `d` stands for the pair `( lo hi )` with the high cell on top.
| Word | Stack | Description |
|---|---|---|
| `S>D` | `( n -- d )` | Sign-extends a single to a double. |
| `D+` `D-` | `( d1 d2 -- d3 )` | Double add and subtract, with carry or borrow. |
| `DNEGATE` | `( d -- -d )` | Negates a double. |
| `DABS` | `( d -- \|d\| )` | Pushes the absolute value of a double. |
| `DMAX` `DMIN` | `( d1 d2 -- d3 )` | Pushes the larger or smaller double, compared as signed values. |
| `D<` | `( d1 d2 -- flag )` | Signed less-than on doubles. |
| `D=` | `( d1 d2 -- flag )` | Equality on doubles. |
| `D0=` | `( d -- flag )` | True if the double is zero. |
| `D0<` | `( d -- flag )` | True if the double is negative. |
| `D2*` | `( d -- d*2 )` | Shifts a double left by one bit across both cells. |
| `D2/` | `( d -- d/2 )` | Shifts a double right by one bit (arithmetic shift) across both cells. |
| `2DROP` | `( x1 x2 -- )` | Drops a cell pair. |
| `2DUP` | `( x1 x2 -- x1 x2 x1 x2 )` | Duplicates a cell pair. |
| `2SWAP` | `( p1 p2 -- p2 p1 )` | Swaps two cell pairs. |
| `2OVER` | `( p1 p2 -- p1 p2 p1 )` | Copies the second pair to the top. |
| `2ROT` | `( p1 p2 p3 -- p2 p3 p1 )` | Rotates three cell pairs. |
| `2>R` | `( x1 x2 -- ) ( R: -- x1 x2 )` | Moves a pair to the return stack. |
| `2R>` | `( -- x1 x2 ) ( R: x1 x2 -- )` | Moves a pair back from the return stack. |
| `2R@` | `( -- x1 x2 ) ( R: x1 x2 -- x1 x2 )` | Copies a pair from the return stack. |
## 8. Number formatting and output
`src/word_source/format_words.c`
| Word | Stack | Description |
|---|---|---|
| `.` | `( n -- )` | Prints a signed number in the current `BASE`, followed by a space. |
| `.R` | `( n width -- )` | Prints a signed number right-aligned in a field `width` characters wide. |
| `U.` | `( u -- )` | Prints an unsigned number followed by a space. |
| `U.R` | `( u width -- )` | Prints an unsigned number right-aligned. |
| `D.` | `( d -- )` | Prints a signed double. |
| `D.R` | `( d width -- )` | Prints a signed double right-aligned. |
| `.S` | `( -- )` | Prints the data stack without changing it. This is the main debugging aid. |
| `?` | `( addr -- )` | Prints the cell at `addr`; it is the same as `@ .`. |
| `DUMP` | `( addr u -- )` | Prints a hex and ASCII dump of `u` bytes starting at `addr`. |
| `<#` | `( -- )` | Starts pictured numeric output by resetting the conversion buffer. |
| `#` | `( ud -- ud' )` | Converts one digit (`ud mod BASE`) into the buffer. It also accepts a single signed cell and converts its magnitude. |
| `#S` | `( ud -- 0 0 )` | Converts digits until the value is zero, always producing at least one digit. It accepts a single cell the same way `#` does. |
| `HOLD` | `( c -- )` | Inserts the character `c` into the pictured output buffer. |
| `SIGN` | `( n -- )` | Inserts `-` if `n` is negative. |
| `#>` | `( ud -- c-addr u )` | Ends conversion and leaves the string. It is tolerant: it pops `ud` only if one is present. |
| `BASE` | `( -- addr )` | Pushes the address of the number-conversion radix variable. |
| `DECIMAL` `HEX` `OCTAL` | `( -- )` | Sets `BASE` to 10, 16, or 8. |
Example: `: .$ ( n -- ) <# # # 46 HOLD #S #> TYPE ;` prints `1234` as `12.34`.
## 9. Strings, parsing, and input
`src/word_source/string_words.c`. The comparison and search words below also accept a counted string in place of an
`addr u` pair; they detect it when the first byte at `addr` equals `u`.
| Word | Stack | Description |
|---|---|---|
| `COUNT` | `( c-addr1 -- c-addr2 u )` | Converts a counted string (length byte followed by characters) into an address and length. |
| `EXPECT` | `( addr u -- )` | Reads up to `u` characters from the terminal into `addr` and stores the count read in `SPAN`. |
| `SPAN` | `( -- addr )` | Pushes the address of the variable that holds the count from the last `EXPECT`. |
| `QUERY` | `( -- )` | Reads a line into `TIB` and resets `>IN`. |
| `TIB` | `( -- addr )` | Pushes the address of the terminal input buffer. |
| `>IN` | `( -- addr )` | Pushes the address of the offset into the current input source. |
| `SOURCE` | `( -- addr u )` | Pushes the current input buffer and its length. |
| `WORD` | `( c -- c-addr )` | Skips leading `c` characters, parses up to the next `c`, and returns a counted string. The usual form is `BL WORD`. |
| `BL` | `( -- 32 )` | Pushes the ASCII space character. |
| `S"` **IMM** | `( "ccc<">" -- c-addr u )` | In interpret mode, stores the string at `HERE` and pushes it. When compiling, it compiles `(s")` followed by the inline text. |
| `(s")` | `( -- c-addr u )` | Runtime for a compiled `S"`: reads the inline `[len][chars][pad]` block and skips the IP past it. The compiler inserts it; you do not call it directly. |
| `[']` **IMM** | `( "name" -- xt )` | While compiling, compiles the xt of `name` as a literal. In interpret mode it behaves like `'`. |
| `LITERAL` `[LITERAL]` | – | Placeholders that do nothing. `LITERAL` is re-registered in §17 (the working version); `[LITERAL]` has no replacement and still does nothing. |
| `CONVERT` | `( d1 addr1 -- d2 addr2 )` | Accumulates the digits at `addr1+1…` into `d1` and stops at the first non-digit. This is a simplified version. |
| `NUMBER` | `( c-addr -- n flag )` | Converts a counted string to a number. Only base 10 is supported, and `flag` shows whether it succeeded. |
| `ENCLOSE` | `( addr c -- addr n1 n2 n3 )` | The classic FIG parser: gives the offsets of the start of the token, the delimiter after it, and the next character. |
| `-TRAILING` | `( addr u -- addr u' )` | Removes trailing spaces from the length. |
| `CMOVE` | `( src dst u -- )` | Copies bytes upward from low to high addresses. It is safe for overlapping ranges when `dst ≤ src`. |
| `CMOVE>` | `( src dst u -- )` | Copies bytes downward from high to low addresses. It is safe for overlapping ranges when `dst > src`. |
| `COMPARE` | `( a1 u1 a2 u2 -- n )` | Compares two strings case-sensitively and returns `-1`, `0`, or `1`. |
| `SEARCH` | `( a1 u1 a2 u2 -- a3 u3 flag )` | Finds string 2 inside string 1. If found, it returns the tail starting at the match and `-1`. If not, it returns string 1 unchanged and `0`. |
| `SCAN` | `( addr u c -- addr' u' )` | Advances to the first occurrence of `c`. If there is none, it returns the end of the string and `0`. |
| `SKIP` | `( addr u c -- addr' u' )` | Skips leading occurrences of `c`. |
| `BLANK` | `( addr u -- )` | Fills `u` bytes with spaces. |
## 10. Terminal I/O
`src/word_source/io_words.c`
| Word | Stack | Description |
|---|---|---|
| `EMIT` | `( c -- )` | Prints one character. |
| `CR` | `( -- )` | Prints a newline. |
| `KEY` | `( -- c )` | Waits for a character and pushes it. |
| `?TERMINAL` | `( -- flag )` | True if a key is waiting. It does not block. |
| `TYPE` | `( c-addr u -- )` | Prints `u` characters. |
| `SPACE` | `( -- )` | Prints one space. |
| `SPACES` | `( n -- )` | Prints `n` spaces. |
| `."` **IMM** | `( "ccc<">" -- )` | In interpret mode, prints the string immediately. When compiling, it compiles `(do-string)` and the inline text. |
| `(do-string)` | `( -- )` | Runtime for a compiled `."`: prints the inline string and skips the IP past it. The compiler inserts it; you do not call it directly. |
## 11. Blocks and mass storage
`src/word_source/block_words.c`. A block is 1024 bytes, viewed as 16 lines of 64 characters. Block numbers are
LBNs (logical block numbers) in one address space that spans every attached device.
| Word | Stack | Description |
|---|---|---|
| `BLOCK` | `( u -- addr )` | Returns the VM address of the buffer holding block `u`, reading it from disk if needed. It does not mark the buffer dirty. |
| `BUFFER` | `( u -- addr )` | Assigns a buffer to block `u` *without* reading from disk and marks it dirty. Use it when you will overwrite the whole block. |
| `UPDATE` | `( -- )` | Marks the current (`SCR`) block dirty and syncs it to the C block layer. |
| `SAVE-BUFFERS` | `( -- )` | Writes every dirty buffer to disk. |
| `EMPTY-BUFFERS` | `( -- )` | Discards every buffer **without** writing it and zeroes the user block window. |
| `FLUSH` | `( -- )` | Runs `SAVE-BUFFERS` and then invalidates all buffers. |
| `LOAD` | `( u -- )` | Sets `SCR` to `u` and interprets the 1024 bytes of block `u` as FORTH source. Block 0 cannot be loaded. |
| `THRU` | `( u1 u2 -- )` | Loads blocks `u1` through `u2`, including both ends. |
| `-->` | `( -- )` | Inside a block being loaded, continues interpreting at the next block. |
| `LIST` | `( u -- )` | Sets `SCR` to `u` and prints the block. |
| `SCR` | `( -- addr )` | Pushes the address of the variable holding the block number last listed or loaded. |
| `BLK-CONFIRM-FORMAT` | `( lbn -- )` | Commits the container format of the device that owns `lbn`. Until this has run, the block layer **refuses every write** to that device. Only the owner (for example Artemis) should call it, and only after checking the disk contents are safe to touch. |
| `RELOCATE-BLOCK` | `( home target -- )` | Moves the contents of `home` to `target` and redirects all later access to `home` through `target`. It is a mechanical primitive: it does not check whether `target` is free or owned by the caller. |
| `BLK-ACL-ALLOW@` | `( blk -- allow )` | Reads the cached allow/deny flag of a block's ACL. |
| `BLK-ACL-ALLOW!` | `( allow blk -- )` | Sets a block's cached allow/deny flag. |
| `BLK-ACL-TTL@` | `( blk -- ttl )` | Reads a block's ACL TTL countdown. |
| `BLK-ACL-TTL!` | `( ttl blk -- )` | Sets a block's ACL TTL countdown. |
| `BLK-OWNER@` | `( blk -- fp )` | Pushes the 8-byte owner fingerprint as the raw bits of one cell. There is no `BLK-OWNER!`: ownership is set only in C, during MINT or birth. |
| `BLK-ATTACH` | `( dev-ptr -- ok? )` | Registers an already-open `blkio_dev_t*`, passed as a raw pointer cell, in the unified LBN space. The pointer is trusted without checks. Artemis's USB-attach handler uses it. |
## 12. Dictionary space
`src/word_source/dictionary_words.c`
| Word | Stack | Description |
|---|---|---|
| `HERE` | `( -- addr )` | Pushes the next free byte in the dictionary. |
| `ALIGN` | `( -- )` | Rounds `HERE` up to the next 8-byte cell boundary. |
| `ALLOT` | `( n -- )` | Reserves `n` bytes at `HERE`. A negative `n` gives space back. |
| `,` | `( x -- )` | Compiles one cell at `HERE` and advances `HERE`. |
| `C,` | `( c -- )` | Compiles one byte. |
| `2,` | `( x-lo x-hi -- )` | Compiles two cells, low cell first. |
| `PAD` | `( -- addr )` | Pushes the address of a 512-byte scratch buffer near the top of memory. It is safe for temporary strings. |
| `SP@` | `( -- n )` | Pushes the data stack pointer *index* (`dsp`). An empty stack gives `-1` and one item gives `0`. |
| `SP!` | `( n -- )` | Restores the stack pointer index. It can only shrink the stack, never grow it. |
| `LATEST` | `( -- addr )` | Pushes the address of the most recent definition. |
## 13. Dictionary manipulation
`src/word_source/dictionary_manipulation_words.c`. Header-field words work on raw header addresses. They exist for
FIG and FORTH-79 compatibility; take care with them.
| Word | Stack | Description |
|---|---|---|
| `'` | `( "name" -- xt )` | Parses `name` and pushes its xt. It is not IMMEDIATE; inside a definition, use `[']`. |
| `FIND` | `( "name" -- xt \| 0 )` | **Parses from the input stream**, not from a counted string on the stack. It pushes the entry, or `0` if the word is not found (a miss is not an error). For a counted string already in memory, use `(FIND)` from §14. |
| `SMUDGE` | `( -- )` | **CO.** Toggles the smudge (hidden) bit on the latest word. |
| `HIDDEN` | `( -- )` | **CO.** Sets the latest word's hidden bit (unlike `SMUDGE`, it does not toggle). |
| `>BODY` | `( xt -- addr )` | Pushes the address of the parameter (data) field. |
| `>NAME` | `( xt -- nfa )` | Pushes the name field. |
| `NAME>` | `( nfa -- xt )` | Goes from the name field to the xt. |
| `>LINK` | `( xt -- lfa )` | Pushes the link field. |
| `LINK>` | `( lfa -- xt )` | Follows the link to the next (older) word. |
| `CFA` `LFA` `NFA` `PFA` | `( addr -- addr' )` | FIG-style field-address conversions (code, link, name, and parameter fields). |
| `TRAVERSE` | `( addr n -- addr' )` | Moves across a name field forward (`n=1`) or backward (`n=-1`). |
| `INTERPRET` | `( -- )` | Runs the text interpreter on the rest of the current input. |
## 14. Vocabularies
`src/word_source/vocabulary_words.c`
| Word | Stack | Description |
|---|---|---|
| `VOCABULARY` | `( "name" -- )` | Creates a vocabulary. Running `name` later makes it the `CONTEXT` (search) vocabulary. |
| `DEFINITIONS` | `( -- )` | Sets `CURRENT` to `CONTEXT`, so new definitions go into the vocabulary being searched. |
| `CONTEXT` | `( -- addr )` | Pushes the address of the search-vocabulary pointer. |
| `CURRENT` | `( -- addr )` | Pushes the address of the definition-vocabulary pointer. |
| `FORTH` | `( -- )` | Makes the root `FORTH` vocabulary the context. |
| `ORDER` | `( -- )` | Prints the search order (`CONTEXT`, then `FORTH`) and `CURRENT`. |
| `(FIND)` | `( c-addr -- c-addr 0 \| xt 1 \| xt -1 )` | Looks up a counted string in `CONTEXT` and then in `FORTH`. It returns `1` for an IMMEDIATE word, `-1` for a normal word, and `0` if not found. |
Example: `VOCABULARY GRAPHICS GRAPHICS DEFINITIONS : BOX ... ; FORTH DEFINITIONS`
## 15. System
`src/word_source/system_words.c`
| Word | Stack | Description |
|---|---|---|
| `(` **IMM** | `( "ccc<)>" -- )` | Starts a comment that runs to the closing `)`. |
| `\` **IMM** | `( "ccc<eol>" -- )` | Starts a comment that runs to the end of the line. |
| `EXECUTE` | `( xt -- )` | Runs the word identified by `xt`. |
| `NOP` | `( -- )` | Does nothing. |
| `QUIT` **IMM** | `( -- ) ( R: … -- )` | Clears the return stack and the error flag and returns to the outer interpreter. The data stack is kept. It is refused (sets an error) inside a definition. |
| `ABORT` | `( … -- )` | Clears both stacks and returns to the interpreter. It is **not** reported as an error. |
| `ABORT"` **IMM** | `( flag "ccc<">" -- )` | If `flag` is non-zero, prints the message and runs `ABORT`. It works in both interpret and compile mode. |
| `(ABORT")` | `( flag addr u -- )` | Runtime for a compiled `ABORT"`. The compiler inserts it; you do not call it directly. |
| `COLD` | `( -- )` | Clears both stacks and the error flag, returns to interpret mode, and moves `HERE` back to 1024 if it is higher. Dictionary headers are **not** removed; this is a minimal cold start. |
| `WARM` | `( -- )` | Clears both stacks and the error flag and returns to interpret mode. `HERE` and the dictionary are kept. |
| `BYE` | `( -- )` | Leaves this VM. In a child VM it halts the VM and returns to the parent's REPL. On Hera, the kernel's `BYE` from §34 takes precedence. |
| `REBOOT` | `( c-addr u -- )` | Sets the boot arguments and does a cold reset. Interpret mode only. |
| `SAVE-SYSTEM` | `( -- )` | Takes a simple snapshot of the start of VM memory. |
| `WORDS` | `( -- )` | Lists the words in the current vocabulary. |
| `VLIST` | `( -- )` | Gives a detailed word listing. |
| `SEE` | `( "name" -- )` | Decompiles and shows a definition. |
| `PAGE` | `( -- )` | Clears the screen. |
| `79-STANDARD` | `( -- flag )` | Pushes `-1` when FORTH-79 compliance mode is on. |
## 16. Line editor
`src/word_source/editor_words.c`. The editor works on block `SCR` as 16 lines of 64 characters.
| Word | Stack | Description |
|---|---|---|
| `L` | `( u -- )` | Prints line `u` (0–15) of the current screen. |
| `S` | `( c-addr len u -- )` | Replaces line `u` with the string, padding with spaces or truncating to 64 characters. |
| `SHOW` | `( -- )` | Prints the whole screen with line numbers. |
| `EDIT` | `( u -- )` | Opens a minimal stdin/stdout line-editor shell on block `u`. |
## 17. Defining words and the compiler
`src/word_source/defining_words.c`
| Word | Stack | Description |
|---|---|---|
| `:` **IMM** | `( "name" -- )` | Starts a colon definition and switches to compile mode. The new word stays hidden until `;`. |
| `;` **IMM** | `( -- )` | Compiles `EXIT`, ends the definition, reveals the word, and returns to interpret mode. |
| `CREATE` | `( "name" -- )` | Makes a header whose runtime pushes its data-field address (`HERE` aligned to a cell). It allocates **no** space, so follow it with `ALLOT` or `,`. |
| `VARIABLE` | `( "name" -- )` | Makes a word that pushes the address of one newly allocated cell. |
| `CONSTANT` | `( x "name" -- )` | Makes a word that pushes `x`. |
| `DOES>` **IMM** | `( -- )` | Inside a defining word, ends the create part. Words later made by that defining word run the code after `DOES>` with their body address on the stack. |
| `IMMEDIATE` **IMM** | `( -- )` | Marks the latest definition IMMEDIATE. |
| `STATE` | `( -- addr )` | Pushes the address of the compile-state cell (0 means interpreting). |
| `[` **IMM** | `( -- )` | Switches to interpret mode inside a definition. |
| `]` **IMM** | `( -- )` | Switches to compile mode. |
| `LITERAL` **IMM** | `( x -- )` | Compiles `x` so that it is pushed at runtime. Typical use: `[ 6 7 * ] LITERAL`. |
| `LIT` | `( -- x )` | Runtime for literals: pushes the next inline cell. The compiler inserts it; you do not call it directly. |
| `COMPILE` **IMM** | `( "name" -- )` | Legacy form: compiles a call to `name`. |
| `[COMPILE]` **IMM** | `( "name" -- )` | Compiles `name` even when it is IMMEDIATE. |
| `FORGET` | `( "name" -- )` | Removes `name` and every newer word and moves `HERE` back. Words below `FENCE` cannot be forgotten. |
| `FENCE` | `( -- )` | Moves the `FORGET` boundary up to the current top of the dictionary. A capsule calls it after loading to protect its own words. |
| `does_rt` | – | Internal `DOES>` helper that switches the new child word to DODOES. It is registered only so the threaded code can refer to it; do not call it. |
Example: `: ARRAY ( n "name" -- ) CREATE CELLS ALLOT DOES> ( i -- addr ) SWAP CELLS + ;`
## 18. Control flow
`src/word_source/control_words.c`. Every structure word is **IMM** and **CO**. Branch offsets are in bytes. Up to 64
structures can be nested at compile time (`CF_STACK_MAX`).
| Word | Stack | Description |
|---|---|---|
| `IF` | `( flag -- )` | Runs the following code only if `flag` is non-zero. It compiles `(0BRANCH)`. |
| `ELSE` | `( -- )` | Starts the code that runs when the `IF` flag was zero. |
| `THEN` | `( -- )` | Ends an `IF` or `IF … ELSE` structure. |
| `BEGIN` | `( -- )` | Marks the start of a loop. |
| `UNTIL` | `( flag -- )` | Loops back to `BEGIN` while `flag` is zero. |
| `AGAIN` | `( -- )` | Loops back to `BEGIN` unconditionally. Leave with `EXIT` or `ABORT`. |
| `WHILE` | `( flag -- )` | In `BEGIN … WHILE … REPEAT`, leaves the loop when `flag` is zero. |
| `REPEAT` | `( -- )` | Jumps back to `BEGIN` and resolves the exit of `WHILE`. |
| `DO` | `( limit start -- ) ( R: -- limit index )` | Starts a counted loop that always runs at least once. |
| `?DO` | `( limit start -- )` | Like `DO`, but skips the loop body when `start = limit`. |
| `LOOP` | `( -- )` | Adds 1 to the index and loops while `index < limit`. |
| `+LOOP` | `( n -- )` | Adds `n` to the index. For `n ≥ 0` it continues while `index < limit`; for `n < 0` it continues while `index ≥ limit`. |
| `LEAVE` | `( -- )` | Exits the innermost `DO` loop immediately: it sets the index to the limit and jumps past `LOOP`. |
| `I` | `( -- index )` | Pushes the index of the innermost loop. It is not IMMEDIATE. |
| `J` | `( -- index )` | Pushes the index of the next outer loop. |
| `UNLOOP` | `( -- ) ( R: limit index -- )` | Drops the loop parameters. Use it before `EXIT` inside a `DO` loop. |
| `EXIT` | `( -- )` | Returns from the current colon definition. Using it in interpret mode is an error. |
| `CASE` | `( x -- x )` | Starts a case structure. |
| `OF` | `( x v -- \| x )` | If `x = v`, drops both and runs the clause; otherwise keeps `x` and skips to the next `OF`. |
| `ENDOF` | `( -- )` | Ends an `OF` clause and jumps to `ENDCASE`. |
| `ENDCASE` | `( x -- )` | Drops the selector and resolves every `ENDOF` jump. |
| `(BRANCH)` | `( -- )` | Runtime: unconditional relative branch. The compiler inserts it; you do not call it directly. |
| `(0BRANCH)` | `( flag -- )` | Runtime: branches when `flag` is 0. The compiler inserts it; you do not call it directly. |
| `(DO)` `(?DO)` | `( limit start -- )` | Runtime for loop entry. The compiler inserts them; you do not call them directly. |
| `(LOOP)` `(+LOOP)` | `( -- )` / `( n -- )` | Runtime for loop increment and test. The compiler inserts them; you do not call them directly. |
| `(LEAVE)` | `( -- )` | Runtime for `LEAVE`: sets index to limit. The compiler inserts it; you do not call it directly. |
Examples:
```forth
: COUNTDOWN ( n -- ) BEGIN DUP . 1- DUP 0= UNTIL DROP ;
: TABLE ( -- ) 5 0 DO 5 0 DO I J * 4 .R LOOP CR LOOP ;
: COLOR ( n -- ) CASE 0 OF ." red" ENDOF 1 OF ." green" ENDOF ." ?" ENDCASE ;
```
## 19. StarForth extensions
`src/word_source/starforth_words.c`. These words are registered in both `FORTH` and the `STARFORTH` vocabulary.
| Word | Stack | Description |
|---|---|---|
| `ENTROPY@` | `( xt -- n )` | Pushes the `execution_heat` counter of a word. The name says "entropy", but the value is execution heat. Registered only in the `STARFORTH` vocabulary. |
| `ENTROPY!` | `( n xt -- )` | Sets a word's `execution_heat` counter. Registered only in the `STARFORTH` vocabulary. |
| `WORD-ENTROPY` | `( -- )` | Prints the execution heat of every word. |
| `RESET-ENTROPY` | `( -- )` | Sets every heat counter to zero. |
| `TOP-WORDS` | `( n -- )` | Prints the `n` hottest words. |
| `(-` | `( "ccc<)>" -- )` | A comment that marks metadata blocks to extract into `init.4th`. It consumes input up to `)`. |
| `INIT` | `( -- )` | Reads `./capsules/core/init.4th`, copies its blocks from block 1 onward, and runs them. |
| `VERSION` | `( -- )` | Prints `StarForth v<ver> <arch> <variant> <timestamp>`. |
| `SEED` | `( n -- )` | Seeds the PRNG so random sequences can be reproduced. |
| `RANDOM` | `( lo hi -- n )` | Pushes a pseudo-random number in `[lo, hi]`, including both ends. |
| `WAIT` | `( n -- )` | Waits `n` heartbeat ticks by calling `vm_tick()` `n` times. It counts heartbeats, not wall-clock time, so it behaves the same on amd64, aarch64, and riscv64. |
| `HEARTBEAT-TICKS@` | `( -- n )` | Pushes the canonical heartbeat tick count (Loop #7). The project uses this as its clock. It is read-only. |
| `ZUSE-AUTHENTICATE` | `( -- )` | Sets `zuse_session = 1`. The write happens only in C. |
| `ZUSE-SESSION?` | `( -- flag )` | True if `ZUSE-AUTHENTICATE` has run during this boot. It is read-only. |
| `ZUSE-PUBKEY@` | `( i -- u )` | Pushes 8-byte little-endian chunk `i` (0–3) of Zuse's Ed25519 **public** key. An out-of-range `i` pushes 0 and sets an error. The private seed is never exposed. |
| `ZUSE-CERT-INSTALLED?` | `( -- flag )` | True once the one-time certificate fuse has been blown. |
## 20. Word-level ACL
`src/word_source/acl_words.c`. Every word takes an `xt`, obtained with `'` or `[']`. Writes are silently ignored for
a **pinned** word.
| Word | Stack | Description |
|---|---|---|
| `ACL-MODE@` | `( xt -- mode )` | Pushes the enforcement mode: 0 is STRICT (the decision is permanent) and 1 is TTL (the decision is rechecked when the countdown expires). |
| `ACL-MODE!` | `( mode xt -- )` | Sets the enforcement mode. |
| `ACL-TTL@` | `( xt -- n )` | Pushes the TTL countdown. At 0 in TTL mode, the interpreter calls `acl_recheck()`. |
| `ACL-TTL!` | `( n xt -- )` | Sets the TTL, clamped to `[0, UINT32_MAX]`. |
| `ACL-ALLOW@` | `( xt -- flag )` | Pushes the cached decision: `-1` means allowed and `0` means denied. |
| `ACL-ALLOW!` | `( flag xt -- )` | Sets the cached decision; any non-zero value means allowed. |
| `ACL-PINNED?` | `( xt -- flag )` | True if the ACL fields are pinned and therefore immutable. |
| `ACL-PIN` | `( xt -- )` | Pins the word. **This is one-way**: no FORTH word can unpin it. |
| `ACL-HEAT@` | `( xt -- heat )` | Pushes the execution heat. `ACL.4th` uses it to calibrate TTLs. |
| `ACL-WORD-ID` | `( xt -- id )` | Pushes the word's stable `word_id`. It never changes, so it is safe to use as a table index. |
| `ACL-INHERIT` | `( src-xt dst-xt -- )` | Copies the mode from `src` to `dst` and resets `dst`: unpinned, TTL 0, allowed. It is written in C because only C may clear a pin. |
| `ACL-INIT-PRIMITIVES` | `( -- )` | For every unpinned word, sets TTL to 0, allow to 1, and mode to TTL. `ACL-BOOT` calls it. |
## 21. Physics: benchmark and diagnostics
`src/word_source/physics_benchmark_words.c`. These are interactive diagnostics that print to the console.
| Word | Stack | Description |
|---|---|---|
| `BENCH-DICT-LOOKUP` | `( iterations -- )` | Benchmarks dictionary lookup and records Q48.16 latencies. Use at least 10,000 iterations; 100,000 is the standard run and 1,000,000 is a stress test. |
| `PHYSICS-CACHE-STATS` | `( -- )` | Prints hot-words cache statistics. |
| `PHYSICS-TOGGLE-CACHE` | `( -- )` | Turns the hot-words cache on or off, for A/B testing. |
| `PHYSICS-RESET-STATS` | `( -- )` | Resets the cache statistics. |
| `PHYSICS-BUILD-INFO` | `( -- )` | Prints the variant's build configuration. |
| `PHYSICS-BAYESIAN-REPORT` | `( addr -- )` | Prints a Bayesian comparison of the current cache statistics against the baseline stored at `addr`. |
> `physics_diagnostic_words.c` also defines `PHYSICS-WORD-METRICS`, `PHYSICS-CALC-KNOBS`, `PHYSICS-BURN ( n -- )`
> and `PHYSICS-SHOW-FEEDBACK`, but nothing calls `register_physics_diagnostic_words()`, so **none of them are in
> the dictionary** at this commit.
## 22. Physics: pipelining diagnostics
`src/word_source/physics_pipelining_diagnostic_words.c`
| Word | Stack | Description |
|---|---|---|
| `PIPELINING-SHOW-STATS` | `( "name" -- )` | Prints the word-to-word transition metrics of `name`. |
| `PIPELINING-SHOW-TOP-TRANSITIONS` | `( "name" n -- )` | Prints the `n` words that most often follow `name`. |
| `PIPELINING-ANALYZE-WORD` | `( "name" -- )` | Prints a full analysis of one word's transitions, with hints for reading them. |
| `PIPELINING-STATS` | `( -- )` | Prints pipelining statistics aggregated across the whole dictionary. |
| `PIPELINING-RESET-ALL` | `( -- )` | Clears all transition metrics. |
| `PIPELINING-ENABLE` | `( -- )` | Placeholder. Pipelining is switched on or off at compile time. |
## 23. Physics: freeze, heat, and decay
`src/word_source/physics_freeze_words.c`. Words are named by `c-addr u` strings, for example `S" DUP" HEAT@`. An
unknown name is not an error.
| Word | Stack | Description |
|---|---|---|
| `FREEZE-WORD` | `( c-addr u -- )` | Sets `WORD_FROZEN` on the word, so Loop #3 decay stops lowering its heat. |
| `UNFREEZE-WORD` | `( c-addr u -- )` | Clears `WORD_FROZEN` and leaves `WORD_PINNED` alone. |
| `FROZEN?` | `( c-addr u -- flag )` | True if the word is frozen. An unknown word gives `0`. |
| `HEAT!` | `( heat c-addr u -- )` | Writes `execution_heat` directly, bypassing Loops #1 and #3. For testing only. |
| `HEAT@` | `( c-addr u -- heat )` | Reads `execution_heat`. An unknown word gives `0`. |
| `SHOW-HEAT` | `( c-addr u -- )` | Prints `NAME: HEAT (frozen) (pinned)`. |
| `ALL-HEATS` | `( -- )` | Prints up to 1024 words sorted by heat, hottest first. |
| `DECAY-RATE@` | `( -- q )` | Pushes the base decay rate per µs (`DECAY_RATE_PER_US_Q16`) in Q48.16. |
| `FREEZE-CRITICAL` | `( -- )` | Freezes 21 core words: `DUP DROP SWAP OVER ROT @ ! C@ C! EXECUTE IF THEN ELSE DO LOOP BEGIN UNTIL REPEAT . EMIT CR`. |
## 24. Dictionary heat optimisation
`src/word_source/dictionary_heat_diagnostic_words.c`
| Word | Stack | Description |
|---|---|---|
| `HEAT-PERCENTILES` | `( -- p25 p50 p75 )` | Pushes the current heat percentile thresholds, with the 75th on top. |
| `LOOKUP-STRATEGY@` | `( -- n )` | Pushes the lookup strategy: 0 is naive (newest-first linear scan) and 1 is heat-aware (hot bucket first). |
| `LOOKUP-STRATEGY!` | `( n -- )` | Sets the strategy. Only 0 or 1 is accepted; other values are silently ignored. |
| `REORG-BUCKETS` | `( -- )` | Re-sorts the lookup buckets by heat and refreshes the percentiles immediately, without waiting for the heartbeat. |
| `SHOW-HEAT-OPTIMIZATION` | `( -- )` | Prints the strategy, the percentiles, and the hot, warm, and cool zones. |
| `COMPARE-LOOKUPS` | `( iterations -- )` | Benchmarks naive against heat-aware lookup and prints the speedup. It restores the original strategy afterwards. |
## 25. Logging
`src/word_source/log_words.c`
| Word | Stack | Description |
|---|---|---|
| `LOG-ERROR` `LOG-WARN` `LOG-INFO` `LOG-TEST` `LOG-DEBUG` | `( -- level )` | Push the log level constants. |
| `LOG-LEVEL!` | `( level -- )` | Sets the active log filter, clamped to `[LOG-ERROR, LOG-DEBUG]`. |
| `LOG-LEVEL@` | `( -- level )` | Pushes the current log level. |
| `LOG-ERROR"` `LOG-WARN"` `LOG-INFO"` `LOG-TEST"` `LOG-DEBUG"` **IMM** | `( "ccc<">" -- )` | Log a literal string at that level. In interpret mode the string is logged immediately; when compiling, a runtime word and the inline string are compiled. |
| `LOG-ERROR-STR` `LOG-WARN-STR` `LOG-INFO-STR` `LOG-TEST-STR` `LOG-DEBUG-STR` | `( c-addr u -- )` | Log a string taken from the stack at that level. |
| `(do-log-error)` `(do-log-warn)` `(do-log-info)` `(do-log-test)` `(do-log-debug)` | `( -- )` | Runtimes for the compiled `LOG-*"` words. The compiler inserts them; you do not call them directly. |
| `(LOG-APPEND-RAW)` **K** | `( level timestamp c-addr u -- )` | Appends a raw entry to the kernel log ring, attributed to the calling VM (or `HADES`). It reports errors on the console, not through `log_message`, to avoid recursion. |
Example: `: CHECK ( n -- ) 0< IF LOG-WARN" negative input" THEN ;`
## 26. Q48.16 fixed-point math
`src/word_source/q48_words.c`. A value is `n × 65536`. The underlying type is **unsigned** `uint64_t`; see
[§36](#36-implementation-quirks-to-know) for what that means for negative values.
| Word | Stack | Description |
|---|---|---|
| `Q.+` `Q.-` | `( q1 q2 -- q3 )` | Add and subtract. |
| `Q.*` | `( q1 q2 -- q3 )` | Multiplies, computing `(a*b) >> 16`. |
| `Q./` | `( q1 q2 -- q3 )` | Divides, computing `(a << 16) / b`. **Division by zero returns 0** and sets no error. |
| `Q.ABS` | `( q -- \|q\| )` | Absolute value, treating the top bit as a sign bit. |
| `Q.NEG` | `( q -- -q )` | Two's-complement negation. |
| `Q.LOG` | `( q -- ln q )` | Natural logarithm by Newton-Raphson. Requires `q > 0`. |
| `Q.EXP` | `( q -- e^q )` | Exponential by Taylor series. |
| `Q.SQRT` | `( q -- √q )` | Square root by Newton-Raphson. |
| `Q.SIN` `Q.COS` | `( q -- q' )` | Sine and cosine of an angle in radians. The argument is reduced to [-π, π] and then a Taylor series is applied. |
| `Q.FROM-INT` | `( n -- q )` | Converts an integer to Q48.16 as `n << 16`. **A negative `n` becomes 0.** |
| `Q.TO-INT` | `( q -- n )` | Converts to an integer as `q >> 16`, truncating. |
| `Q.1` | `( -- 65536 )` | Pushes 1.0. |
| `Q.0` | `( -- 0 )` | Pushes 0.0. |
| `Q.SCALE` | `( -- 65536 )` | Pushes the scale factor; the same value as `Q.1`. |
| `Q.=` | `( q1 q2 -- flag )` | Equality. |
| `Q.<` `Q.>` | `( q1 q2 -- flag )` | Comparison, done **unsigned**. |
| `Q.0=` | `( q -- flag )` | True if the value is zero. |
| `Q.MAX` `Q.MIN` | `( q1 q2 -- q3 )` | Maximum and minimum, compared **unsigned**. |
| `Q.PRINT` | `( q -- )` | Prints the value as `int.fffff ` with five fractional digits. |
Example: `3 Q.FROM-INT Q.SQRT Q.PRINT` prints approximately `1.732`.
## 27. Inference engine (SSM, L8, and Bayes)
`src/word_source/inference_words.c`
| Word | Stack | Description |
|---|---|---|
| `INFER-RUN` | `( -- )` | Runs the full inference engine on this VM's rolling window and dictionary heat, and caches the results. |
| `INFER-WINDOW@` | `( -- u )` | Pushes the last inferred optimal window width. |
| `INFER-DECAY@` | `( -- q )` | Pushes the last inferred decay slope. |
| `INFER-VARIANCE@` | `( -- q )` | Pushes the last inferred variance. |
| `INFER-FIT@` | `( -- q )` | Pushes the last fit quality. |
| `INFER-EARLY-EXIT@` | `( -- flag )` | True if the last run exited early. |
| `Q.VARIANCE` | `( addr u -- q )` | Pushes the variance of `u` uint64 cells at `addr`. |
| `INFER-DECAY-SLOPE` | `( addr u -- q )` | Fits a decay slope to the array by linear regression. |
| `INFER-WINDOW-WIDTH` | `( addr u -- n )` | Computes the optimal window width for the array. |
| `WINDOW-DIVERSITY` | `( -- u )` | Pushes the number of distinct words in the rolling window. |
| `L8-MODE` | `( -- n )` | Pushes the current L8 (legacy 16-mode) Jacquard selection. |
| `L8-UPDATE` | `( entropy cv temporal stability -- )` | Feeds four Q48.16 metrics to `ssm_l8_update()`. `stability` is on top of the stack. |
| `L8-APPLY` | `( -- )` | Applies the currently selected L8 mode. |
| `L8-TABLE-FORCE` | `( idx -- )` | Forces the adaptive 128-config table to `idx & 127` as though the UCB bandit had picked it, and applies it. Unlike `L8-UPDATE` and `L8-APPLY`, this choice is not overwritten at the next heartbeat trial. Use it for DoE campaigns. |
| `BAYES-CACHE-MEAN` `BAYES-CACHE-LOWER` `BAYES-CACHE-UPPER` | `( -- q )` | Push the posterior mean latency and the 95 % credible bounds for hot-words cache hits. |
| `BAYES-BUCKET-MEAN` `BAYES-BUCKET-LOWER` `BAYES-BUCKET-UPPER` | `( -- q )` | Push the same three values for bucket searches. |
## 28. DEFER and IS
`src/word_source/defer_words.c`
| Word | Stack | Description |
|---|---|---|
| `DEFER` | `( "name" -- )` | Creates a vectored word. Running it before an action has been set with `IS` sets `vm->error`. |
| `IS` | `( xt "name" -- )` | Sets the action of `name`. It is refused unless `name` was created by `DEFER`. |
| `DEFER@` | `( "name" -- xt )` | Pushes the current action of a deferred word. |
Example: `DEFER GREET : HI ." hi" ; ' HI IS GREET GREET`
## 29. Framebuffer (Hestia only)
`src/word_source/framebuffer_words.c`. These words are registered only in the Hestia VM, by `capsule_birth_baby()`,
and not by `register_forth79_words()`.
| Word | Stack | Description |
|---|---|---|
| `PLOT` | `( x y color -- )` | Writes one raw pixel. The origin is top-left and Y increases downward. |
| `FB-WIDTH` | `( -- n )` | Pushes the framebuffer width in pixels. |
| `FB-HEIGHT` | `( -- n )` | Pushes the framebuffer height in pixels. |
## 30. Keyboard
`src/word_source/keyboard_words.c`. These are diagnostics for the console fabric. On a platform without the device,
each word pushes `0`.
| Word | Stack | Description |
|---|---|---|
| `KBD-SCAN` | `( -- sc -1 \| 0 )` | amd64 i8042: pops one raw XT scancode from the queue, if there is one. |
| `KBD-DEBUG` | `( -- isr spurious )` | amd64: pushes the i8042 interrupt count and the spurious-interrupt count. |
| `VKBD-EVENT` | `( -- code value -1 \| 0 )` | riscv64 and aarch64 virtio-input: pops one Linux-style `EV_KEY` code and value. |
| `VKBD-DEBUG` | `( -- isr )` | riscv64 and aarch64: pushes the virtio-input interrupt count. |
| `KEY-EVENT` | `( -- keycode pressed -1 \| 0 )` | The unified event on every architecture: pops one keycode with its pressed (1) or released (0) state. |
| `ALT+TAB` | `( -- )` | Switches the console between text and graphics, the same as the physical Alt+Tab. |
## 31. TrueType text
`src/word_source/ttf_words.c`
| Word | Stack | Description |
|---|---|---|
| `TTF-TEXT` **K** | `( c-addr u x y size color -- )` | Draws the string with the TrueType renderer at pixel `(x, y)` in the given size and color. |
## 32. REPL scrollback
`src/word_source/scroll_words.c`
| Word | Stack | Description |
|---|---|---|
| `SCROLL-BACK` **K** | `( n -- )` | Scrolls the REPL view back `n` lines. |
| `SCROLL-FWD` **K** | `( n -- )` | Scrolls the REPL view forward `n` lines, toward the live output. |
## 33. Kernel REPL and DoE hooks
`src/starkernel/repl.c` and `src/starkernel/doe_log.c`
| Word | Stack | Description |
|---|---|---|
| `HB-ON` **K** | `( -- )` | Turns on per-tick DoE instrumentation. |
| `HB-OFF` **K** | `( -- )` | Turns off per-tick DoE instrumentation. |
| `BLK-ATTACH-ACK` **K** | `( dev-ptr ok? -- )` | The acknowledgement Artemis sends to Hera after `BLK-ATTACH`, delivered with `VM-EXEC`. On success, Hera runs the deferred Zuse or WIREBIND attach. |
| `KH-BLK-ATTACH-SEND` **K** | `( c-addr u -- ok? )` | Sends a payload to Hera through kernel-Hermes. The sender and receiver are worked out in C, so the caller cannot spoof them. A refused send is logged. |
| `KH-ELEVATE-SEND` **K** | `( c-addr u -- ok? )` | The same mechanism for elevation requests. Nothing calls it at present. |
## 34. Hera (Mama) and child-VM words
`src/starkernel/capsule/mama_forth_words.c`. These are **K** only. Hera receives every word in this section, in
both `FORTH` and the `MAMA` vocabulary. Child VMs receive only `STOP EXEC USE BIRTH CAPSULE-BIRTH VM-EXEC VM-CALL
VM-HEAT VM-ERROR? SWITCH-MARK-WORK` and the `STADIUM-*` words. VM names are matched without regard to case.
### VM lifecycle
| Word | Stack | Description |
|---|---|---|
| `BIRTH` | `( c-addr u -- )` | Births a VM from its capsule (`S" Artemis"` loads `artemis:init.4th`). If the VM is already live, nothing happens. `Hera` is rejected. |
| `KILL` | `( c-addr u -- )` | Destroys a VM. Hera cannot be killed, and killing a dead VM does nothing. |
| `START` | `( c-addr u -- )` | Enters the VM's REPL and blocks until it runs `STOP` or `BYE`. It refuses a VM that is LIVE, DEAD, or STILLBORN. |
| `STOP` | `( -- )` | Halts the current VM, so its REPL loop returns. |
| `USE` | `( c-addr u -- )` | Redirects console input to the named VM without nesting the C stack, and changes the prompt to `[Name]`. `S" Hera" USE` switches back to Hera. |
| `EXEC` | `( c-addr u -- )` | Runs a named capsule inside the current VM. |
| `EJECT` | `( -- )` | Cleanly detaches the identity attached through USB home blocks: it flushes the user VM and kills it, or logs Zuse out. |
| `CONNECT-HERMES` | `( -- )` | Enters Hermes's REPL, birthing Hermes first if needed. |
| `CONNECT-ARTEMIS` | `( -- )` | Enters Artemis's REPL, birthing Artemis first if needed. |
| `BYE` | `( -- )` | **On Hera:** reaps every child and then cold-resets the machine. |
### Cross-VM execution (compudynamics)
| Word | Stack | Description |
|---|---|---|
| `VM-EXEC` | `( cmd-a cmd-u name-a name-u -- )` | Injects a command into the named VM and runs it immediately without blocking. Example: `S" DOE-WORK" S" Hermes" VM-EXEC`. |
| `VM-CALL` | `( cmd-a cmd-u name-a name-u -- n )` | Like `VM-EXEC`, then pops the target's top of stack onto the caller's stack. If the target left nothing, it pushes 0 and sets an error. |
| `VM-STEP` | `( c-addr u -- )` | Gives the named VM one REPL turn: one prompt, one line, then it returns. |
| `VM-HEAT` | `( c-addr u -- q )` | Pushes the VM's `execution_heat_q48`. An unknown VM gives 0 and prints nothing. |
| `VM-ERROR?` | `( c-addr u -- flag )` | True if the VM has `vm->error` set. An unknown VM gives `0`. |
| `VM-COUNT` | `( -- n )` | Pushes the number of registered VMs. |
| `VM-CONSERVED?` | `( -- flag )` | True if the total fleet heat is within ε of `Q.1`. |
| `VM-PHYSICS-STATUS` | `( -- )` | Prints the fleet physics report. |
| `SWITCH-MARK-WORK` | `( c-addr u -- )` | Marks a VM as having work, which makes it eligible for a context switch. The message path calls it, and it ignores bad names silently. |
| `MAMA-VM-ID` | `( -- 0 0 )` | Pushes Hera's 128-bit VM ID, which is all zeros. |
| `NAME>XT` | `( c-addr u -- xt \| 0 )` | Looks up a name held in a data buffer. A miss gives `0`. |
### Capsules
| Word | Stack | Description |
|---|---|---|
| `CAPSULE-COUNT` | `( -- n )` | Pushes the number of entries in the capsule directory. |
| `CAPSULE@` | `( idx -- desc \| 0 )` | Pushes the descriptor for the capsule at `idx`. |
| `CAPSULE-HASH@` | `( desc -- hash )` | Pushes the capsule's content hash. |
| `CAPSULE-FLAGS@` | `( desc -- flags )` | Pushes the capsule's flags. |
| `CAPSULE-LEN@` | `( desc -- len )` | Pushes the capsule's payload length. |
| `CAPSULE-BIRTH` | `( id -- vmid-lo vmid-hi )` | Births an unnamed VM from a production (p) capsule and pushes its 128-bit ID. On failure both cells are all ones. |
| `CAPSULE-RUN` | `( id -- )` | Runs an experiment (e) capsule on Hera. |
| `CAPSULE-TEST` | `( -- )` | Prints a message confirming the capsule system is running. |
| `WORKER-BIRTH` | `( cap-a cap-u name-a name-u -- ok? )` | Births a named, `VM-EXEC`-addressable worker from any p-capsule, with no identity attached. |
| `UNATTENDED-BIRTH` | `( cap-a cap-u name-a name-u -- ok? )` | Births a VM and installs the verified identity whose `UNATTENDED-ID-UUID` and `UNATTENDED-ID-CERT` the capsule defined. |
| `CONSOLE-ATTACH` | `( name-a name-u -- ok? )` | Pairs a new console VM with the live VM registered as `<name>~user`. |
### Identity, Zuse, and diagnostics
| Word | Stack | Description |
|---|---|---|
| `MINT` | `( fn-a fn-u un-a un-u em-a em-u ph-a ph-u restrict? -- ok? )` | Mints an identity onto the attached USB drive. The full name and username are required; pass an empty string for email or phone to leave them out. A non-zero `restrict?` selects the locked-down personality that allows only FORTH-79 and FORTH-83 words. |
| `MINT-SCRATCH` | same as `MINT` | Mints onto the scratch device instead, prints the UUID, and pushes 1 or 0. |
| `MINT-SCRATCH-EMIT` | `( -- ok? )` | Prints the last scratch mint as FORTH source (`CREATE UNATTENDED-ID-UUID` / `-CERT` byte lists) for copying by hand. It refuses (pushes 0) if no mint has succeeded. |
| `ZUSE-ELIGIBILITY-ADD` | `( c-addr -- ok? )` | Adds the 32-byte Ed25519 public key at `c-addr` to Zuse's elevation list. |
| `ZUSE-ELIGIBLE?` | `( c-addr -- flag )` | Checks whether a key is on the list. It fails closed. |
| `ELEVATE-PUBKEY-UNPACK` | `( pk0 pk1 pk2 pk3 buf -- )` | Rebuilds a 32-byte public key from four cells. It is the inverse of `ZUSE-PUBKEY@`. |
| `RUNCAP-TEST` | `( c-addr u -- ok? rc )` | Diagnostic: runs `capsule_runcap_birth()` against the current home-blocks drive. |
| `PAIR-TEST` | `( c-addr u -- ok? )` | Diagnostic: births a console VM and a `<name>~user` VM as a pair. |
### Stadium (heat accounting)
Heat values are Q48.16. Each word works only on the calling VM's own quota.
| Word | Stack | Description |
|---|---|---|
| `STADIUM-ADMIT` | `( identity heat behaviour -- cell \| -1 )` | Admits a patron. `behaviour` must be 0–3. |
| `STADIUM-EVICT` | `( cell -- flag )` | Reaps the patron in `cell`. It is refused if the cell is out of range, not resident, pinned, or blocked by what it contains. |
| `STADIUM-HEAT@` | `( cell -- heat )` | Reads a resident cell's heat. A cell that is not the caller's gives 0. |
| `STADIUM-HEAT!` | `( heat cell -- )` | Writes a cell's heat, pulling the difference from the reservoir or pushing it back. An increase the reservoir cannot cover is silently refused. |
| `STADIUM-RES@` | `( -- heat )` | Pushes the VM's reservoir balance. |
| `STADIUM-RES-PULL` | `( qty -- got )` | Pulls up to `qty` from the reservoir and pushes the amount actually taken. |
| `STADIUM-RES-PUSH` | `( heat -- )` | Credits heat back to the reservoir. |
| `STADIUM-WORD-HEAT` | `( -- heat )` | Pushes the total heat held by this VM's word-execution residents. |
## 35. Hosted lifecycle stubs
`src/word_source/lifecycle_words_hosted.c`. These are **H** only; `main.c` registers them. Each word only logs
`"<WORD> <name> (hosted)"`. They exist so that capsule scripts also parse in hosted builds.
| Word | Stack | Description |
|---|---|---|
| `BIRTH` `KILL` `PAUSE` `RESUME` `USE` | `( c-addr u -- )` | No-op stubs that only write a log line. |
---
## 36. Implementation quirks to know
These behaviours differ from what a FORTH-79 or ANS programmer would expect. Each was checked against the C source.
1. **`ROLL` counts from the bottom of the stack.** With `1 2 3` on the stack, `1 ROLL` gives `2 3 1`; ANS gives
`1 3 2`. The test suite (`stack_words_test.c`) asserts the current behaviour, so it looks intended. Portable code
should use `SWAP` and `ROT`.
2. **`PICK` is 0-based,** as in ANS. FORTH-79's `PICK` was 1-based.
3. **`FIND` parses the input stream.** It does not take a counted string. Use `(FIND)` for a counted string or
`NAME>XT` (kernel only) for a name in a buffer.
4. **The Q48.16 type is unsigned.** `Q.<`, `Q.>`, `Q.MIN`, `Q.MAX` and `Q.PRINT` treat a negative Q value as a huge
positive one, and `Q.FROM-INT` turns a negative integer into 0. `Q.ABS` and `Q.NEG` do treat the top bit as a sign.
5. **`Q./` by zero returns 0 without setting an error,** while the integer `/`, `MOD`, and `*/` all set `vm->error`.
6. **`[LITERAL]` does nothing,** and `LITERAL` works only because §17 registers it again after the placeholder.
7. **`MOD`, `/MOD`, `*/`, and `*/MOD` are registered twice.** The mixed-arithmetic versions (§6) are the ones used.
8. **Four `PHYSICS-*` diagnostic words are not registered.** `PHYSICS-WORD-METRICS`, `PHYSICS-CALC-KNOBS`,
`PHYSICS-BURN` and `PHYSICS-SHOW-FEEDBACK` are defined in C but never added to the dictionary.
9. **`does_rt` is a visible dictionary entry.** It is an internal helper; do not call it.
10. **The `STARFORTH` vocabulary registers its words twice** (once in `FORTH`, once in `STARFORTH`). Hera does the
same with `MAMA`. As a result, those names appear twice in `WORDS` output.
+112
View File
@@ -0,0 +1,112 @@
# docs/book/Makefile -- the single LithosAnanke book, built from LaTeX.
#
# Run from the repo root (the root Makefile does this):
# make docs -> build/docs/LithosAnanke.pdf
# make docs TARGET=<board> -> build/docs/LithosAnanke-<board>.pdf
# (same book + that board's appendix)
#
# docs/book/main.tex is the master. Markdown sources are converted by pandoc
# into LaTeX fragments under build/docs/gen/ and \input from main.tex; the
# Markdown stays the source of truth until a chapter is rewritten in LaTeX.
# Figures and tables that present data read the CSV directly at LaTeX time
# (pgfplots / pgfplotstable) and cite it with \datasource{path}; see
# docs/book/README.md.
BOOK_DIR := docs/book
OUT := build/docs
GEN := $(OUT)/gen
BOARD ?=
BOARD_NAME ?=
VARIANT := $(if $(BOARD),$(BOARD),book)
VAR_DIR := $(OUT)/$(VARIANT)
PDF := $(OUT)/LithosAnanke$(if $(BOARD),-$(BOARD)).pdf
PANDOC ?= pandoc
LATEXMK ?= latexmk
PANDOC_FILTERS := $(BOOK_DIR)/pandoc/table-widths.lua $(BOOK_DIR)/pandoc/code-breaks.lua
PANDOC_FLAGS := -f gfm -t latex --top-level-division=chapter --wrap=preserve \
$(foreach f,$(PANDOC_FILTERS),--lua-filter=$(f))
# fragment name : Markdown source. The order of \input lines is main.tex's.
CHAPTERS := \
ontology:docs/ONTOLOGY.md \
roadmap:docs/ROADMAP.md \
boards:boards/README.md \
justification:docs/v4.0.0/JUSTIFICATION.md \
decomposition:docs/v4.0.0/DECOMPOSITION.md \
fabric-0:docs/fabric/FABRIC-0.md \
fabric-1:docs/fabric/FABRIC-1.md \
fabric-2:docs/fabric/FABRIC-2.md \
fabric-3:docs/fabric/FABRIC-3.md \
fabric-3-5:docs/fabric/FABRIC-3.5.md \
fabric-3-6:docs/fabric/FABRIC-3.6.md \
fabric-3-7:docs/fabric/FABRIC-3.7.md \
fabric-4:docs/fabric/FABRIC-4.md
ifneq ($(BOARD),)
ifeq ($(wildcard boards/$(BOARD)/README.md),)
$(error boards/$(BOARD)/README.md is missing: it is the board's appendix)
endif
CHAPTERS += board-$(BOARD):boards/$(BOARD)/README.md
endif
chapter_name = $(word 1,$(subst :, ,$(1)))
chapter_src = $(word 2,$(subst :, ,$(1)))
FRAGMENTS := $(foreach c,$(CHAPTERS),$(GEN)/$(call chapter_name,$(c)).tex)
.PHONY: all tools FORCE
all: $(PDF)
tools:
@missing=""; \
for t in $(PANDOC) $(LATEXMK) xelatex; do command -v $$t >/dev/null 2>&1 || missing="$$missing $$t"; done; \
if [ -n "$$missing" ]; then \
echo "Error: make docs needs:$$missing"; \
echo " sudo apt-get install -y pandoc latexmk texlive-xetex texlive-latex-extra texlive-pictures texlive-fonts-recommended fonts-dejavu fonts-dejavu-extra"; \
echo " (or a user-local TinyTeX + pandoc in ~/.local; see docs/book/README.md)"; \
exit 1; \
fi
# --id-prefix keeps heading labels unique across documents that reuse
# section names ("Summary", "Open questions", ...).
define chapter_rule
$(GEN)/$(call chapter_name,$(1)).tex: $(call chapter_src,$(1)) $(PANDOC_FILTERS) | tools
@mkdir -p $(GEN)
@echo " PANDOC $$< -> $$@"
@$(PANDOC) $(PANDOC_FLAGS) --id-prefix=$(call chapter_name,$(1))- $$< -o $$@
endef
$(foreach c,$(CHAPTERS),$(eval $(call chapter_rule,$(c))))
# Pandoc's syntax-highlighting macros, taken from the installed pandoc so the
# fragments and their macros always come from the same version.
$(GEN)/pandoc-highlighting.tex: $(BOOK_DIR)/pandoc/highlighting.latex | tools
@mkdir -p $(GEN)
@printf '```c\nx\n```\n' | $(PANDOC) -f gfm -t latex -s --template=$< -o $@
# Per-build facts the book prints: the commit every \datasource refers to,
# and which board appendix (if any) is included.
$(VAR_DIR)/meta.tex: FORCE
@mkdir -p $(VAR_DIR)
@{ \
c=$$(git rev-parse --short=12 HEAD 2>/dev/null || echo unknown); \
git diff --quiet HEAD -- 2>/dev/null || c="$$c (+ uncommitted changes)"; \
printf '\\newcommand{\\bookcommit}{%s}\n' "$$c"; \
printf '\\newcommand{\\bookdate}{%s}\n' "$$(date -u +%Y-%m-%d)"; \
printf '\\def\\bookboard{%s}\n' "$(BOARD_NAME)"; \
$(if $(BOARD),printf '\\newcommand{\\bookboardappendix}{board-%s}\n' "$(BOARD)";) \
} > $@.tmp
@cmp -s $@.tmp $@ && rm -f $@.tmp || mv $@.tmp $@
FORCE:
TEXINPUTS_BOOK := $(abspath $(VAR_DIR)):$(abspath $(GEN)):$(abspath $(BOOK_DIR)):
$(PDF): $(BOOK_DIR)/main.tex $(FRAGMENTS) $(GEN)/pandoc-highlighting.tex $(VAR_DIR)/meta.tex | tools
@echo " LATEXMK $(BOOK_DIR)/main.tex -> $@"
@TEXINPUTS=$(TEXINPUTS_BOOK) $(LATEXMK) -xelatex -interaction=nonstopmode -halt-on-error \
-file-line-error -outdir=$(VAR_DIR) $(BOOK_DIR)/main.tex > $(VAR_DIR)/latexmk.log 2>&1 || { \
grep -A4 -E '^(.*:[0-9]+:|!)' $(VAR_DIR)/main.log | head -40; \
echo "Error: LaTeX failed; full log: $(VAR_DIR)/main.log"; exit 1; }
@cp $(VAR_DIR)/main.pdf $@
@echo " PDF $@"
+66
View File
@@ -0,0 +1,66 @@
# docs/book/
`make docs` builds one PDF, `build/docs/LithosAnanke.pdf`, from `main.tex` with xelatex.
`make docs TARGET=<board>` builds `build/docs/LithosAnanke-<board>.pdf`: the same book with
`boards/<board>/README.md` added as an appendix.
Tools: `sudo apt-get install -y pandoc latexmk texlive-xetex texlive-latex-extra texlive-pictures texlive-fonts-recommended fonts-dejavu fonts-dejavu-extra`
Without root, a user-local TeX Live works too (this is how the book was first built):
TinyTeX (`curl -sL https://yihui.org/tinytex/install-bin-unix.sh | sh`) plus
`tlmgr install latexmk xetex fontspec pgf pgfplots booktabs multirow fancyvrb fvextra lineno upquote ulem enumitem newunicodechar bookmark hyperref geometry xcolor graphics tools etoolbox fancyhdr truncate amsfonts`,
and the pandoc release tarball unpacked into `~/.local`.
The installed DejaVu decides italics: `fonts-dejavu-core` alone has no serif italic, so
`main.tex` falls back to a slanted upright face; `fonts-dejavu-extra` gives real italics.
## How it fits together
- `main.tex` is the master file. It sets the parts and the chapter order.
- Chapters that are still Markdown are listed in `Makefile` (`CHAPTERS`, as `name:source.md`).
At build time pandoc converts each one into `build/docs/gen/<name>.tex`, and `main.tex`
includes it with `\input{<name>}`. The Markdown stays the source until the chapter is
rewritten in LaTeX. At that point the `.tex` moves into this directory and the `CHAPTERS`
entry is removed.
- `pandoc/highlighting.latex` extracts the code-highlighting macros from the installed pandoc,
so the macros and the fragments always come from the same pandoc version.
- Two Lua filters run on every chapter. `pandoc/table-widths.lua` gives wide tables
proportional wrapping columns, because pandoc's gfm reader leaves column widths unset.
`pandoc/code-breaks.lua` lets long inline identifiers and paths break after `_ / . - :`.
Code blocks wrap through fvextra (`breaklines`).
- `build/docs/<book|board>/meta.tex` is written on every build. It records the commit (and
whether there were uncommitted changes), the date, and the board.
## Rule for data
Any figure or table that shows measured numbers is generated from the CSV when the book is
built. Numbers are never typed in by hand:
```latex
\begin{tikzpicture}
\begin{axis}[xlabel=run, ylabel=K]
\addplot table[col sep=comma, x=run, y=K]{experiments/<campaign>/results.csv};
\end{axis}
\end{tikzpicture}
\datasource{experiments/<campaign>/results.csv}
```
Use `\pgfplotstabletypeset[col sep=comma]{...}` for tables. `\datasource` prints the CSV path
and the commit, so a reader can find the exact file the figure was drawn from. CSV paths are
repo-relative, because xelatex runs from the repo root.
## Older pipelines to fold in or retire
These still exist and still have their own targets. Each one needs a decision during the
curation pass: move it into this book, or retire it.
| Source | Current target | Produces |
|---|---|---|
| `docs/formal/vol1-vm-physics`, `vol2-kernel`, `vol3-research` | `make -C docs/formal vols` | three volume PDFs |
| `docs/formal/dev-guide`, `user-guide`, `cookbook` | `make -C docs/formal books` | three practitioner PDFs |
| `docs/formal/experiments`, `proofs`, `ssrn`, `patent` | `make -C docs/formal standalone` | four standalone PDFs |
| Doxygen (`Doxyfile`) | `make -C docs/formal doxygen` | API reference PDF |
| `scripts/generate-doxygen-appendix.sh` | `make -f v3/Makefile api-docs` | AsciiDoc API appendix |
| `docs/src/internal/formal/*.thy` | `make -f v3/Makefile docs-isabelle` | Isabelle report |
| `scripts/asciidoc-to-latex.sh` | `make -f v3/Makefile docs-latex` | `docs/latex/` |
| `docs/SSRN_companion/Math_Companion_SSRN.tex` | `make -f v3/Makefile math-companion` | SSRN math companion |
+161
View File
@@ -0,0 +1,161 @@
% docs/book/main.tex -- master file of the single LithosAnanke book.
%
% Built by `make docs [TARGET=<board>]` (docs/book/Makefile) with xelatex.
% Fragments named below (ontology, fabric-0, ...) are generated by pandoc
% from the Markdown listed in docs/book/Makefile's CHAPTERS into
% build/docs/gen/; meta.tex is generated per build. Neither is committed.
\documentclass[11pt,openany]{book}
\usepackage{amsmath,amssymb}
\usepackage{fontspec}
% fonts-dejavu-core has no serif/sans italics (fonts-dejavu-extra does); when a
% shape is missing, slant the upright one rather than silently drop emphasis.
\setmainfont{DejaVu Serif}[AutoFakeSlant=0.2]
\setsansfont{DejaVu Sans}[AutoFakeSlant=0.2]
\setmonofont{DejaVu Sans Mono}[Scale=0.85]
\newfontfamily\symbolfont{DejaVu Sans}
\usepackage[letterpaper,margin=1in]{geometry}
\usepackage{xcolor}
\usepackage{graphicx}
\usepackage{longtable,booktabs,array,calc,multirow}
\usepackage{fancyvrb}
\usepackage[normalem]{ulem}
\usepackage{enumitem}
\usepackage{newunicodechar}
\usepackage{pgfplots}
\usepackage{pgfplotstable}
\pgfplotsset{compat=1.18}
\usepackage[hidelinks]{hyperref}
\usepackage{bookmark}
% --- what pandoc's LaTeX fragments expect (normally from its own template)
\providecommand{\tightlist}{\setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}}
\providecommand{\pandocbounded}[1]{#1}
\providecommand{\st}[1]{\sout{#1}}
\newcounter{none} % pandoc: {\def\LTcaptype{none} ...} marks unnumbered tables
\usepackage{etoolbox}
\makeatletter
\def\fnum@table{\tablename~\thetable}
\patchcmd\longtable{\par}{\if@noskipsec\mbox{}\fi\par}{}{}
\makeatother
\usepackage{fvextra}
\input{pandoc-highlighting}
% Code and diagrams are wider than the page in places: wrap, never overflow.
\RecustomVerbatimEnvironment{Highlighting}{Verbatim}{commandchars=\\\{\},breaklines,breakanywhere,fontsize=\small}
\RecustomVerbatimEnvironment{verbatim}{Verbatim}{breaklines,breakanywhere,fontsize=\small}
\setlistdepth{9}
\renewlist{itemize}{itemize}{9}
\setlist[itemize]{label=\textbullet}
\renewlist{enumerate}{enumerate}{9}
\setlist[enumerate]{label=\arabic*.}
% Running heads: chapter on the left page, section on the right, both cut to
% the page width (several design-record titles are a full sentence long).
\usepackage{fancyhdr}
\usepackage[fit]{truncate}
\pagestyle{fancy}
\fancyhf{}
\renewcommand{\chaptermark}[1]{\markboth{#1}{}}
\renewcommand{\sectionmark}[1]{\markright{#1}}
\fancyhead[LE]{\small\truncate{\dimexpr\headwidth-3em}{\leftmark}}
\fancyhead[RO]{\small\truncate{\dimexpr\headwidth-3em}{\rightmark}}
\fancyhead[RE,LO]{\small\thepage}
\renewcommand{\headrulewidth}{0.4pt}
\setlength{\headheight}{14pt}
\fancypagestyle{plain}{\fancyhf{}\fancyfoot[C]{\small\thepage}\renewcommand{\headrulewidth}{0pt}}
\setlength{\emergencystretch}{3em}
\setlength{\parindent}{0pt}
\setlength{\parskip}{0.5em}
% --- glyphs DejaVu Serif lacks: the status marks used across the docs
\newunicodechar{✅}{{\symbolfont ✔}}
\newunicodechar{✓}{{\symbolfont ✓}}
\newunicodechar{❌}{{\symbolfont ✘}}
\newunicodechar{✗}{{\symbolfont ✗}}
\newunicodechar{⬜}{{\symbolfont ☐}}
\newunicodechar{⭐}{{\symbolfont ★}}
\newunicodechar{🔶}{{\symbolfont ◆}}
\newunicodechar{⚠}{{\symbolfont ⚠}}
\newunicodechar{📋}{}
\newunicodechar{🐛}{[bug]}
\newunicodechar{🎯}{[goal]}
\newunicodechar{🟡}{{\symbolfont ●}}
\newunicodechar{📍}{{\symbolfont ▸}}
\newunicodechar{🔓}{[unlocked]}
\newunicodechar{❓}{?}
\newunicodechar{⟺}{\ensuremath{\Longleftrightarrow}}
\newunicodechar{⋯}{\ensuremath{\cdots}}
\newunicodechar{^^^^fe0f}{}
% --- the documents number their own sections (§III.1, D-3, ...)
\setcounter{secnumdepth}{0}
\setcounter{tocdepth}{1}
\input{meta}
% \datasource{path/to/data.csv}: cite the data behind a figure or table.
% Every figure or table of measured numbers reads its CSV at build time
% (\addplot table / \pgfplotstabletypeset) and is followed by this line, so
% the reader can find the exact file at the exact commit.
\newcommand{\datasource}[1]{%
\par{\small Data: \texttt{\detokenize{#1}} at commit \texttt{\bookcommit}.}\par}
\title{LithosAnanke and StarForth}
\author{}
\date{Built \bookdate{} from commit \texttt{\bookcommit}%
\ifx\bookboard\empty\else\\Board appendix: \bookboard\fi}
\begin{document}
\frontmatter
\maketitle
\tableofcontents
\chapter{About this book}
This book is generated from the repository by \texttt{make docs}. Each
chapter below is still maintained as the Markdown file named in the table and
converted at build time; a chapter moves into LaTeX source when it is
rewritten. Part~III is the design record: those documents are archival and
are reproduced as written, including their struck-through corrections.
\begin{longtable}{@{}ll@{}}
\toprule
Chapter & Source \\
\midrule
\endhead
Ontology & \texttt{docs/ONTOLOGY.md} \\
Roadmap & \texttt{docs/ROADMAP.md} \\
Boot targets & \texttt{boards/README.md} \\
v4 justification & \texttt{docs/v4.0.0/JUSTIFICATION.md} \\
v4 decomposition & \texttt{docs/v4.0.0/DECOMPOSITION.md} \\
FABRIC-0 \dots{} FABRIC-4 & \texttt{docs/fabric/FABRIC-*.md} \\
Board appendix (\texttt{TARGET=}) & \texttt{boards/<board>/README.md} \\
\bottomrule
\end{longtable}
\mainmatter
\part{The system}
\input{ontology}
\input{roadmap}
\input{boards}
\part{StarForth v4}
\input{justification}
\input{decomposition}
\part{Design record (FABRIC series)}
\input{fabric-0}
\input{fabric-1}
\input{fabric-2}
\input{fabric-3}
\input{fabric-3-5}
\input{fabric-3-6}
\input{fabric-3-7}
\input{fabric-4}
\ifdefined\bookboardappendix
\appendix
\input{\bookboardappendix}
\fi
\end{document}
+29
View File
@@ -0,0 +1,29 @@
-- docs/book/pandoc/code-breaks.lua
--
-- Inline code in the design record is full of long identifiers and paths
-- (g_wirebind_attached_username, kernel/src/arch/riscv64/...), which LaTeX
-- cannot break, so they run into the margin. Emit inline code as \texttt
-- ourselves, escaped the same way pandoc does, with a break opportunity
-- after each separator character.
local BREAK_AFTER = { ["_"] = true, ["/"] = true, ["."] = true, ["-"] = true,
[":"] = true, [","] = true, ["("] = true, ["="] = true }
local ESCAPE = {
["\\"] = "\\textbackslash{}", ["{"] = "\\{", ["}"] = "\\}",
["$"] = "\\$", ["&"] = "\\&", ["#"] = "\\#", ["%"] = "\\%", ["_"] = "\\_",
["^"] = "\\textasciicircum{}", ["~"] = "\\textasciitilde{}",
["'"] = "\\textquotesingle{}", ["`"] = "\\textasciigrave{}",
[" "] = "\\ ",
}
function Code(el)
if not FORMAT:match("latex") then return nil end
local out = {}
for _, cp in utf8.codes(el.text) do
local ch = utf8.char(cp)
out[#out + 1] = ESCAPE[ch] or ch
if BREAK_AFTER[ch] then out[#out + 1] = "\\allowbreak{}" end
end
return pandoc.RawInline("latex", "\\texttt{" .. table.concat(out) .. "}")
end
+3
View File
@@ -0,0 +1,3 @@
$if(highlighting-macros)$
$highlighting-macros$
$endif$
+55
View File
@@ -0,0 +1,55 @@
-- docs/book/pandoc/table-widths.lua
--
-- pandoc's gfm reader leaves every column width unset, so the LaTeX writer
-- emits `l` columns and a table with long cells runs off the page. For any
-- table wider than WIDE characters, give each column a width proportional
-- to its longest cell (clamped to [MIN_COL, MAX_COL] characters); the
-- writer then emits wrapping p{} columns that together fill \linewidth.
local WIDE = 72
local MIN_COL = 6
local MAX_COL = 60
local stringify = pandoc.utils.stringify
local function measure(rows, widths)
for _, row in ipairs(rows) do
local col = 1
for _, cell in ipairs(row.cells) do
local span = cell.col_span or 1
local len = utf8.len(stringify(cell.contents)) or #stringify(cell.contents)
local per = len / span
for c = col, col + span - 1 do
if per > (widths[c] or 0) then widths[c] = per end
end
col = col + span
end
end
end
function Table(tbl)
local n = #tbl.colspecs
local widths = {}
for i = 1, n do widths[i] = 0 end
measure(tbl.head.rows, widths)
for _, body in ipairs(tbl.bodies) do
measure(body.head, widths)
measure(body.body, widths)
end
measure(tbl.foot.rows, widths)
local total = 0
for i = 1, n do total = total + widths[i] end
if total <= WIDE then return nil end
local sum = 0
for i = 1, n do
widths[i] = math.max(MIN_COL, math.min(MAX_COL, widths[i]))
sum = sum + widths[i]
end
for i = 1, n do
tbl.colspecs[i] = { tbl.colspecs[i][1], widths[i] / sum }
end
return tbl
end
+125 -31
View File
@@ -1,37 +1,26 @@
# FABRIC-3.5.md — the Tripod/kernel reshuffle
**Status: REOPENED 2026-09-19, by direct instruction ("reopen 3.5 and write it up as a gap
analysis section"), to add §XXXI — a gap-analysis sweep of the whole FABRIC set. The design
phase remains closed; §XXXI adds findings, not new design.**
**Status: CLOSED/ARCHIVAL as of 2026-09-22, at tag `v2.1.0`.** Per §XXVI.5's own close
condition ("this document closes when the tag exists"). Produced the full design ruling for
the Tripod/kernel reshuffle: Hermes moves from a FORTH VM into the kernel as kernel-Hermes; the
Tripod becomes Hera/Artemis/Hestia; every design question this document opened is ruled (§XXX.7
/ §XLI's punch list). Execution against that design is recorded in `FABRIC-3.6.md`, not here —
all five phases (0–5) closed there, ending in the `v2.1.0` tag this header names. **Nothing is
carried forward and no successor is created** — this document is not part of the
`FABRIC-0 → -1 → -2 → -3` chain (§XXVI.5), so closing it hands nothing to a successor.
**`FABRIC-3.md` remains open, living and authoritative for its own topic** (bare metal boot);
nothing here ever superseded it.
> **Prior status, kept rather than overwritten: DESIGN PHASE CLOSED as of 2026-09-19, by
> direct instruction ("close the document for now"). Not yet archival.** That close stood for
> the duration of the sweep and its substance is unchanged — every design question was and
> remains ruled. The reopen is recorded rather than the close deleted, per this series' own
> rule against silently rewriting a prior state.
**What is closed, and what is deliberately not.** Every design question this document opened is
ruled — see §XXX.7. **No code has been written and no code is authorized.** The execution
sequence (§XXVI.6, as amended by §XXX.7) is untouched: the surgical strip, the build, the
Isabelle/HOL pass, the documentation sweep, the SBOM, the merge to `master`, and the `v2.1.0`
tag all remain ahead.
**This is therefore a narrower close than §XXVI.5 specified**, and the difference is stated
rather than glossed. §XXVI.5 ruled that this document closes *when the tag exists*, in
`FABRIC-2.md`'s CLOSED/ARCHIVAL form, naming the tag it closed at. **The tag does not exist**,
so claiming that close would be a label running ahead of the real state — the exact error
`FABRIC-3.md` §I.2 corrected when it rolled `LITHOS_VERSION` back, and the same standard §XXIX
applied to the LTS question. **The archival close specified by §XXVI.5 still stands and still
happens at `v2.1.0`.** This header is the provisional one the instruction's own "for now" asks
for.
**Nothing is carried forward and no successor is created.** As §XXVI.5 establishes, this
document is not part of the `FABRIC-0 → -1 → -2 → -3` chain, so closing it hands nothing to a
successor. **`FABRIC-3.md` remains open, living and authoritative for its own topic** (bare
metal boot); nothing here supersedes it. The live artifact from this document is its punch list
(§XXVI.6 / §XXX.7) — that, not this header, is what the build works from.
**Open by design, not by omission:** items 10–17 of §XXVI.6 are execution, not decisions.
> **Prior status headers, kept rather than overwritten, per this series' own rule against
> silently rewriting a prior state:**
>
> **REOPENED 2026-09-19**, by direct instruction ("reopen 3.5 and write it up as a gap analysis
> section"), to add §XXXI — a gap-analysis sweep of the whole FABRIC set.
>
> **DESIGN PHASE CLOSED as of 2026-09-19**, by direct instruction ("close the document for
> now"). Not yet archival at that point — the execution sequence (surgical strip, build,
> Isabelle pass, doc sweep, SBOM, merge, tag) was still entirely ahead. That gap is now closed:
> `FABRIC-3.6.md`'s Phases 0–5 are the execution this header once described as pending.
Three things recorded here are explicitly *outside* this reshuffle and still need their own
authorization — the `.claude/CLAUDE.md`/`MANIFEST.md` documentation reconciliation (§XXVI.1),
the `src/*.c.bak` hygiene question (§XXII.5), and the stray `refs/heads/v2.0.1` branch and
@@ -4593,6 +4582,34 @@ any checkpoint reached *during* the drain sees `depth > 1` and will not drain ag
counter that defines the safe boundary also makes the drain non-reentrant**, with no flag, no
lock and no new state.
**Amendment, found while building task 3.4 (2026-09-22): this claim is true and covers a
different hazard than the one that actually mattered.** Non-reentrant drain (the same message
cannot drain twice) is real, but `g_vm_interpret_depth` was built for *cross-VM* nesting — the
checkpoint's own comment frames it as "nested inside a `VM-EXEC`/`VM-CALL` dispatch **from
another VM's own `vm_interpret()` call**." A different VM means a different `VM` struct, so
`input_buffer`/`input_length`/`input_pos` never collide in that case. **Same-VM reentrancy —
draining calls `vm_interpret()` on the very `vm` whose own `vm_interpret()` call is still
paused on the C stack, mid-word, at the checkpoint that triggered the drain — is a genuinely
separate hazard §XLIII.5 never named.** `FABRIC-3.md` §XX had already flagged this exact class
for Hera specifically: the old messaging pump skipped her because "self-targeting `VM-EXEC`
would hit the same reentrancy class… (`input_buffer`/`input_pos` not being saved by
`vm_state_push`/`pop`)." Read directly: `VMCallState` (`vm_state_push()`/`vm_state_pop()`,
`mama_forth_words.c`) saves only `rsp`/`exit_colon`/`ecw_nesting` — never `input_buffer`,
`input_length`, or `input_pos`. Left unaddressed, the enclosing `vm_interpret()` call's own
while loop would silently lose the rest of its input line or block the moment a drain fires
mid-line — the same failure shape as the `INPUT_BUFFER_SIZE` 256 defect `.claude/CLAUDE.md`
calls non-negotiable, and trap #1 in `FABRIC-3.6.md`'s own START HERE ("a green boot is weak
evidence").
**Not a divergence from this section's ruling — the mechanism is exactly as ruled.** Cursor
preservation is the implementer's own obligation *inside* the ruled mechanism, the same way
§XLIV.1 found `mkcapsule`'s "1024-byte limit" was arithmetically right and operationally
incomplete: fixing one thing while leaving a real gap unnamed. `FABRIC-3.6.md` task 3.4's own
implementation snapshots `vm->input_buffer`/`input_length`/`input_pos`, plus `vm->mode` (a
checkpoint reached mid-colon-definition must not let a drained payload's words compile into
the enclosing definition) and `vm->error`/`abort_requested` (a bad message must not abort the
enclosing execution), around the `vm_interpret()` call, restoring all six afterward.
### XLIII.6 — Three constraints, named rather than discovered later
1. **`INPUT_BUFFER_SIZE` is 1025** — 1024 content bytes plus NUL, and `.claude/CLAUDE.md` calls
@@ -4665,3 +4682,80 @@ not edited from here. `.claude/CLAUDE.md` now carries the full 64×16 rule.
`MANIFEST.md`'s ride the strip per §XXII.5.
- ⬜ **Item 45, NEW** — carry §XLIV.1's 64-char line limit into the `experiments/bare_metal/README.md`
capsule guidance if it repeats the byte framing. **Not checked; flagged.**
---
## XLV. B1, B2, B4 SETTLED by Captain Bob (2026-09-21) — channels negotiate, the switch table is dynamic, payloads are one block
Three of Phase 3's blockers (`FABRIC-3.6.md` B1/B2/B4) ruled in one session. **These are rulings,
recorded as given; the reasoning below is the design consequence, not new design.**
### XLV.1 — B1 (item 27): negotiated channels, pub/sub, ACK/NACK
> **Messaging is publish/subscribe. There is one common channel every VM listens to. A private
> channel is created by request → grant/deny over the common channel, and the granted channel
> is a new topic with its own membership. ACK/NACK negotiation runs throughout.**
This **overrules §XXXIII.5's recommendation** (one flat broadcast membership) in favour of the
negotiation that `messaging.4th`'s CH-REQUEST/ACCEPT/CONFIRM/CLOSE already sketched. Consequences:
- **`SkHermesMembership` (task 2.1) is per-channel.** The flat list is right *per topic*; there
is now one per channel, held in a **dynamic** table with no fixed channel maximum (same
reasoning as XLV.2).
- **The common channel is a permanent topic**; every VM is subscribed from birth.
- **A deny is a NACK.** ACK/NACK are ordinary message types on the existing heat-coupled
allocator (tasks 2.2–2.7), so they draw and decay heat like any message.
- **Advised, not ruled — open for Captain Bob:** ACK the private-channel open and delivery, not
every common-channel message, since ACK-everything roughly doubles message heat draw against
the consumption economy (§XL.4).
- **Advised, not ruled — open:** who may open a channel with whom is policy and belongs in
`ACL.4th` (CLAUDE.md: policy in FORTH, never gated on `zuse_session`); kernel-Hermes should
ask, not decide. Where that hook lives must be settled before the grant task is written.
### XLV.2 — B2 (item 32): the switch table is dynamic, not a constant
> **`SK_SWITCH_MAX_SLOTS` (16) is not to be fixed and static; it becomes a dynamic component.**
Precedent already in the tree: Stadium sizes its VM quota table from RAM at boot
(`stadium_max_vm_count_val`, kmalloc'd, 2026-08-15). The switch table follows the same pattern.
**The concrete sizing rule is not yet specified** and is a task-writing prerequisite.
### XLV.3 — B4 (item 44): payload bound is one block; larger payloads chunk
> **Follow the block convention the `*.4th` files use.**
Read as (confirmed by Captain Bob 2026-09-21): a message payload is bounded to **one block =
1024 bytes** (64×16, §XLIV.1), which matches §XLIII.6.1's `INPUT_BUFFER_SIZE − 1` drain limit;
anything larger is **chunked across messages**. Chunk framing (sequence/last-chunk marking) is
a Phase 3 task-writing prerequisite. Note the 64-char line rule (§XLIV.1) governs *capsule
source blocks*; whether it also constrains message payload content is **not** ruled here.
### XLV.4 — Punch list
- ✅ **Items 27, 32, 44 — SETTLED** (§XLV.1–.3).
- ✅ **NEW** — ACK cadence (XLV.1); ACL hook for channel-open policy (XLV.1); dynamic switch-table
sizing rule (XLV.2); chunk framing (XLV.3). **SETTLED §XLVI.**
- ✅ **Phase 3 task breakdown** — drafted `FABRIC-3.6.md` tasks 3.0–3.11, 2026-09-21.
## XLVI. §XLV.4 sub-items SETTLED by Captain Bob (2026-09-21) — task 3.0 closed
All five `FABRIC-3.6.md` task 3.0 sub-items, plus task 3.3's heat-cost design point, ruled in one
session (all recommended defaults accepted):
- **3.0(a) ACK cadence:** channel-open + delivery only, not every common-channel message —
confirms §XLV.1's own advised default.
- **3.0(b) Channel-open ACL hook location:** a new word in `ACL.4th` itself (e.g.
`HERMES-CHANNEL-OPEN?`), extending its scope from per-word ACL to also cover channel-open
policy, rather than a separate policy file. Keeps "all policy in `ACL.4th`" as one rule with
no exceptions.
- **3.0(c) Dynamic switch-table sizing:** mirrors Stadium's `stadium_max_vm_count_val` pattern —
RAM-derived at boot, no separate sizing formula.
- **3.0(d) Chunk framing:** each chunk carries `(msg_id, seq, is_last)` ahead of up to 1024
bytes of payload; receiver reassembles by `msg_id`+`seq` order.
- **3.0(e) Drain cadence:** one message per outermost-interpret checkpoint (§XLIII.6.3's own
recommendation) — a backlog drains across several checkpoint hits, not in one burst.
- **Task 3.3 heat cost per publish:** one message per subscriber (separate heat draw each),
matching the existing heat-coupled allocator 1:1 — no refcount machinery to add to the
allocator/ledger.
`FABRIC-3.6.md` task 3.0 is closed by this ruling; tasks 3.1–3.7 may now be written precisely.
File diff suppressed because it is too large Load Diff
+205
View File
@@ -0,0 +1,205 @@
# FABRIC-3.7.md — Phase 8 PKI: the elevation entrypoint
**Status: OPEN — design only, no code written or authorized.**
**CORRECTION (2026-09-22, before any code was written against this document): §2's central
claim — that the old `SEND-ELEVATE-REQUEST` passed a raw cross-VM address into `ELEVATE-GRANT`'s
`waddr` — is wrong.** Found while starting Part A's implementation: reading the actual deleted
source (`git show 3e201c8^:capsules/common/messaging.4th`, block 5040) shows
`SEND-ELEVATE-REQUEST` copied the target word's **name as literal character bytes** (via
`ELEVATE-REQ-APPEND`'s `CMOVE`) into a scratch buffer, building the text `S" <name-text>" <pk0>
<pk1> <pk2> <pk3> ELEVATE-GRANT`, and sent *that whole string* to Hera. When Hera's own
interpreter runs `S" <name-text>"`, it allocates a fresh string **in Hera's own memory** and
pushes Hera's own valid address — no numeric cross-VM address ever appears anywhere in this
flow. §2 was written from `ELEVATE-GRANT`'s signature alone, assuming the caller forwarded a raw
address, without first reading how the caller actually built its message. It didn't.
**What is still real, much narrower than originally claimed:** if a future caller ever spliced
*attacker-influenced* text into the name field without checking for an embedded `"` character,
that could break out of the `S" ... "` literal early and inject arbitrary FORTH source, executed
with Hera's privilege. That's an input-validation discipline question for whoever writes the new
caller (validate: no embedded `"`, or just always use a compile-time-fixed literal name, never a
runtime-supplied one) — not an architectural cross-VM-memory defect requiring the buffer/message
redesign §3 originally called for. **§3's proposed mechanism (Hera-side fixed receive buffer,
kernel-constructed integer-literal-only command) is not needed** — the original text-copy
design was already safe against the bug as actually diagnosed. Kept below, struck through, for
traceability, per this series' own rule against silently rewriting a prior state.
Successor to
`FABRIC-3.5.md`/`FABRIC-3.6.md` (both CLOSED/ARCHIVAL at `v2.1.0`) for exactly one topic: the
Ed25519-challenge-response elevation entrypoint that `.claude/CLAUDE.md`'s ACL section names as
Phase 8, the last open item in the word-level ACL system. This is a **new document**, not a
reopening of `FABRIC-3.6.md` — that document's own close header says future fleet work gets its
own document, and this is that.
**Provenance.** Written 2026-09-22, immediately after `FABRIC-3.6.md`'s close, from a design
conversation with Captain Bob about a security concern he raised directly: how to rebuild the
elevation entrypoint that Phase 4's Category B strip left dangling, without reopening a hole.
The design below was proposed, and Captain Bob asked for it in writing here rather than left
only in session memory.
---
## 1. What's dangling, and why
`FABRIC-3.6.md` Phase 4 (Category B strip, 2026-09-22) deleted `capsules/common/messaging.4th`
after every FORTH-owned message type had been cut over to kernel-Hermes. One casualty was
collateral, not intended: `SEND-ELEVATE-REQUEST` (`messaging.4th` block 5040) was the only
caller of both `KH-ELEVATE-SEND` (`src/starkernel/repl.c`) and, transitively, `ELEVATE-GRANT`
(`capsules/zuse-eligibility.4th`, blocks 4021–4022). All three still exist in the tree.
`ELEVATE-GRANT` is still loaded at boot (`capsules/init.4th:18`). Nothing can call it any more.
Captain Bob's decision at the time (`FABRIC-3.6.md`'s own Phase 4 entry): leave it unreachable,
don't patch a caller back in as part of that strip. Phase 8 builds its own entrypoint instead of
resuming this one. **This document is that entrypoint's design.**
<details>
<summary>Original §2/§3 (WRONG — see the correction at the top of this document; kept for
traceability, not current design)</summary>
### 2. The security hole in the old mechanism — found before any code was written
`ELEVATE-GRANT`'s signature, unchanged since it was written:
```
ELEVATE-GRANT ( waddr wu pk0 pk1 pk2 pk3 -- )
```
`waddr`/`wu` are an address/length pair meant to point at the string naming the word to elevate.
`pk0`–`pk3` are the caller's Ed25519 pubkey, packed 8 bytes per cell (`ELEVATE-PUBKEY-UNPACK`,
`mama_forth_words.c`).
**The old `SEND-ELEVATE-REQUEST` computed `waddr` in the *sending* VM's own address space, but
`ELEVATE-GRANT` always executes on Hera** (`ELEVATE-GRANT always runs on Hera` — `repl.c`'s own
comment on `KH-ELEVATE-SEND`, still there). A word's name string lives in the sending VM's
memory. `ELEVATE-GRANT` dereferences `waddr` in Hera's memory. Those are not the same address
space by construction — `vaddr_t` is per-VM.
**Consequence:** whoever controls `waddr` controls what bytes `NAME>XT` reads and resolves as a
word name, in Hera's dictionary, not the caller's. This is not "the string might be malformed" —
it is a primitive for making Hera's own `ELEVATE-GRANT` grant `ACL-ALLOW!`/`ACL-TTL!` on
*whatever dictionary entry the attacker's chosen `waddr` happens to land on*, regardless of what
word name the caller claims to be requesting elevation for. A caller who can influence `waddr`
at all — not forge a signature, not defeat `zuse_eligibility_is_member()`, just choose a number
— has a privilege-escalation primitive against the fleet governor.
This was never exploited (the entrypoint has had zero live callers since the file that called it
was deleted), and is reported here as a design defect found by inspection, not a live incident.
### 3. The fix: never cross an address, only ever cross bytes
This project already solved the general version of this problem once, this same session
(`FABRIC-3.6.md` tasks 3.8/3.9, the payload-aliasing fix): a kernel-Hermes message's payload
must be **copied into the message's own storage**, never a pointer into the sender's memory that
might be reused or freed before the receiver drains it. `SkHermesMessage.payload_buf`
(`include/starkernel/vm/kernel_hermes.h:160`, `SK_HERMES_CHUNK_MAX_PAYLOAD` = 1024 bytes) is
exactly that fix, already built, already proven on all three architectures.
**The elevation entrypoint's hole is the same defect one level up: an address crossing a
boundary it isn't valid on the other side of.** The fix generalizes directly:
1. **Never send `waddr`/`wu` across the kernel-Hermes boundary.** Send the pubkey (32 bytes,
already the right shape for `payload_buf`) and the target word's **name, as literal bytes**,
copied inline into the message payload — not an address, the actual characters. This is
already how `CONSOLE-CMD-EVENT`'s payload works (a command string's bytes, not a pointer to
one), so this isn't a new pattern, it's applying the existing one to the one caller that
still passed a raw address.
2. **On receipt, kernel-Hermes's C drain-checkpoint copies those name bytes into a small,
fixed, kernel-owned buffer that already lives in Hera's own VM memory** — a receive-side
mirror of the existing send-side pattern (`g_kh_elevate_buf`, `repl.c:445`, is the
already-built precedent for "a static buffer this mechanism owns"; this needs its Hera-side
counterpart). The buffer's address is a compile-time constant, known to the kernel, never
computed from anything the caller supplied.
3. **The FORTH command handed to `vm_interpret()` on Hera references only that fixed buffer's
address and length as plain integer literals.** Both are always kernel-controlled. Neither is
ever derived from caller input. `ELEVATE-GRANT` itself does not change — same signature, same
`zuse_eligibility_is_member()` check, same `ACL-ALLOW!`/`ACL-TTL!` grant. Policy logic stays
in FORTH, per `ACL.4th`'s own rule (no new C primitive for policy) — this fix is entirely
about how bytes get from one VM to another, not about who is allowed to grant what.
**Why this closes the hole structurally, not by validation:** there is no string to sanitize and
no address to bounds-check, because the interpreted command never contains anything an attacker
touched except opaque data bytes that get copied, never dereferenced as a pointer, by the
receiving side. The class of bug (cross-address-space pointer confusion) becomes impossible by
construction, the same way `payload_buf` made use-after-free impossible by construction rather
than by careful lifetime tracking.
</details>
## 2 (corrected). What the old mechanism actually did, and the one real gap in it
Re-read from the actual deleted source (`git show 3e201c8^:capsules/common/messaging.4th`,
blocks 5039–5040): `SEND-ELEVATE-REQUEST ( pk3 pk2 pk1 pk0 waddr wu -- )` used `waddr`/`wu` only
to `CMOVE` the target word's **name bytes**, as text, into a scratch buffer
(`ELEVATE-REQ-BUF`/`ELEVATE-REQ-APPEND`) it owned — building the literal string `S"
<name-text>" <pk0> <pk1> <pk2> <pk3> ELEVATE-GRANT` entirely in the *sending* VM's own memory.
Only that finished string — not `waddr` itself — went to `KH-ELEVATE-SEND` and across to Hera.
When Hera's interpreter runs `S" <name-text>"`, Hera's own `S"` allocates a fresh string **in
Hera's own memory** and pushes Hera's own valid address. `waddr`/`wu` never cross the VM
boundary as numbers at any point — only as copied character content. There is no cross-VM
pointer dereference anywhere in this flow.
**The one real, much narrower gap:** the name text is spliced into `S" ... "` with no check for
an embedded `"` character. If a future caller ever passed attacker-influenced text as the name
(none ever did — the word had zero live callers), a `"` in the name would close the string
literal early and let the rest of the name execute as raw FORTH source, with Hera's privilege.
This is a caller-discipline / input-validation question, not an architectural defect: either
always use a compile-time-fixed name literal at the call site (no runtime input, no risk at
all), or validate for an embedded `"` before building the command if a name ever does need to
come from something less trusted than the call site's own source code.
**Net effect on Phase 8 v1's scope:** Part A, as originally conceived in §3 above, is not
needed. If a `SEND-ELEVATE-REQUEST` replacement is ever built, it can follow the original
text-copy design as-is, with the one-line `"`-check added if and only if the name is ever
runtime-supplied rather than a fixed literal. No kernel-Hermes/`repl.c` changes required. Part B
(`capsules/zuse.4th`, gating `ZUSE-ELIGIBILITY-ADD`) stands on its own, independently verified,
unaffected by this correction.
## 4. What Phase 8 actually needs to build
Corrected per §2's re-read above. Concretely, when Phase 8 next picks this up:
- If a caller into `ELEVATE-GRANT` is ever needed again, rebuild it close to the original
`SEND-ELEVATE-REQUEST` shape (`ELEVATE-REQ-BUF`/`ELEVATE-REQ-APPEND`/text-copy into `S" ...
"`) — it was already safe. Add the one-line embedded-`"` check only if the name is ever
runtime-supplied rather than a call-site literal. No `kernel_hermes.c`/`kernel_hermes.h`/
`repl.c` changes needed for this.
- `ELEVATE-GRANT` unchanged either way.
- **Part B is done** (`capsules/zuse.4th`, committed and three-arch verified this session,
2026-09-22) — `ZUSE-ELIGIBILITY-ADD` denied by default, granted only inside `ACL-ZUSE-BOOT`'s
authenticated branch. This closes the actual "grant yourself eligibility with no real drive at
all" path — a real, independently-confirmed gap, unaffected by this correction.
- **Defending against a cloned drive — settled, 2026-09-23: not going to happen, by design.**
Today, WIREBIND/MINT trust whatever identity is stored on an attached thumbdrive with no
challenge at all (confirmed by grep: no `ed25519_sign`/`ed25519_verify` call anywhere in
`capsule_wirebind.c` or `capsule_mint.c`), and the private key seed itself is stored in
plaintext on the drive, read in the same devblock as the pubkey/cert. **This is accepted, not
a gap.** Captain Bob, directly: *"nothing like a pin or a password or secret code or any
bullshit... Everybody has secrets. There's only the drive."* Physical possession of the drive
is the entire, deliberate credential model — a byte-for-byte clone being equivalent to the
real drive is the accepted design, not a defect to close. A PIN/passphrase second factor was
built, live-tested on all three architectures, and fully reverted before commit
(`/home/rajames/.claude/plans/jiggly-cuddling-stallman.md`, now marked rejected; memory
`feedback_no_knowledge_factor_identity`) — **do not revisit a knowledge-factor approach here.**
Any future work in this space needs a fundamentally different mechanism (not something typed
and known) or stays an accepted limitation.
- **Phase 8 v3, 2026-09-23 — done, a distinct and narrower concern from the item above.** The
"accepted limitation" above is about a drive image copied *outside* StarshipOS entirely (e.g.
imaged on an external computer) — that's still accepted, unchanged by this item. Captain Bob
separately asked to close a narrower, different threat: **cloning a device's block content
from *within* StarshipOS's own console**, using its own stock, unpinned words
(`<src> BLOCK <dst> BUFFER 1024 MOVE`/`RELOCATE-BLOCK`). That's now closed — `MOVE`/`CMOVE`/
`CMOVE>`/`RELOCATE-BLOCK` all refuse a same-VM, cross-device copy, verified live on all three
architectures. See `/home/rajames/.claude/plans/jiggly-cuddling-stallman.md`'s "Phase 8 v3"
section for the full design and verification record. The identity record itself
(seed/pubkey/cert) was already unreachable from FORTH before this — this closes the one real
gap the research found: ordinary block content, not the identity record.
## 5. What this document is not
Not a reopening of `FABRIC-3.6.md`, not a change to anything currently built, not an
authorization to write code. Per this series' own convention: design here, execution gets its
own document when the work actually starts, the same relationship `FABRIC-3.5.md` had to
`FABRIC-3.6.md`.
+56 -20
View File
@@ -7,34 +7,70 @@ longer exists here — see `.claude/CLAUDE.md`'s "On the branch topology" note)
---
## Release Versioning Policy (decided 2026-08-28)
## Release Versioning Policy (ratified 2026-09-19, FABRIC-3.5.md §XXX — supersedes the
2026-08-28 policy below)
This is the standing release-versioning policy for LithosAnanke. It is authoritative; the
FABRIC documents track implementation against it, and any permanent change to the policy
belongs here.
- **`X.0.0` — QEMU release.** A whole-number minor in the `X.0` position is the QEMU-only
release: the point where the three-architecture (amd64/aarch64/riscv64) story under QEMU
is complete and defensible on its own terms. Nothing real-hardware-only is required to cut
an `X.0.0`.
- **`X.5.0` — Hardware bare-metal release.** A `X.5` release is the real-hardware release:
the point where the same story transfers to bare metal on real boards (SER5, RasPi 6,
Milk-V, Zynq FPGA). Real-hardware-only work — the per-arch RNG drivers behind the unified
entropy entry point, and real-board boot validation — gates `X.5.0`, not `X.0.0`.
- **Even major numbers are LTS.** A release whose major number (`X`) is even is a
Long-Term-Support release — maintained and supported long-term (bug fixes, security,
backports) rather than a point-in-time cut. Odd major numbers are non-LTS development
lines.
**Superseded 2026-09-19.** The original 2026-08-28 policy (`X.0.0` = QEMU release, `X.5.0` =
hardware bare-metal release, **even** major = LTS) used the minor field to carry a milestone
meaning and had the LTS parity backwards relative to the ratified policy below. It is kept
struck through immediately after this note for historical traceability, not as current rule.
Applied to the two planned releases: the **v2.0.0** cut (even major, so LTS) is the QEMU
release; **v2.5.0** is the hardware bare-metal release that transfers v2.0.0's QEMU story to
real boards. See `FABRIC-2.md` §G for the release-gate punch lists.
- **Major: ODD = LTS line** (non-breaking fixes only — backports, never redesigns). **EVEN =
working line** (breaking changes land here). The line you are on tells you whether breakage
is possible at all — not the major number's parity signaling "supported vs. not" the way the
retired policy read it.
- **Minor** — release within that line. No longer carries a milestone meaning (`X.5.0` no
longer means anything special) — milestones are roadmap entries now, not arithmetic.
- **Patch** — working builds within a release.
- **`LITHOS_VERSION` (kernel) and `VERSION` (embedded StarForth engine) are independent and do
not auto-sync** — do not infer a relationship if they ever coincide.
- **Engine `VERSION` bumps at tag time by what actually changed:** major = any
FORTH-79-visible word semantics change; minor = words added/removed/relocated; patch =
build-only, no dictionary-visible change.
```
v1.0.x — serial-only production (released, historical encoding)
v1.5.x — framebuffer VT100 console milestone (released, historical encoding)
v2.0.0 — QEMU release: three-arch QEMU story complete (released)
v2.0.1 — SER5 hardware-track line (historical; superseded by this policy)
v2.1.0 — Tripod/kernel reshuffle: Hermes into the kernel, Tripod = Hera/Artemis/Hestia
v2.2.0 — amd64 bare-metal bring-up (Beelink SER5)
v2.x — further working releases; riscv64 / aarch64 board bring-up
v3.0.0 — FIRST LTS, when and only when all 5 criteria below are met
```
**LTS criteria (a tag may take an odd major only when all five hold):** real-hardware boot
demonstrated on at least the amd64 reference board, logs committed as audit artifacts; no
known-reproducible silent-failure defect open; ACL Phase 8 closed (or explicitly scoped out in
writing); the `proof/` boundary restated and not contracted relative to the prior LTS; a soak —
architecture unchanged for at least one full release cycle. The first tag meeting all five
takes the odd major — no judgement call.
See `FABRIC-3.5.md` §XXX for the full ratified policy text and reasoning, and `FABRIC-2.md` §G
for the release-gate punch lists this ladder replaced the arithmetic reading of.
<details>
<summary>Historical: the retired 2026-08-28 policy (kept for traceability only)</summary>
- ~~`X.0.0` — QEMU release. A whole-number minor in the `X.0` position is the QEMU-only
release: the point where the three-architecture (amd64/aarch64/riscv64) story under QEMU
is complete and defensible on its own terms.~~
- ~~`X.5.0` — Hardware bare-metal release. Real-hardware-only work gates `X.5.0`, not
`X.0.0`.~~
- ~~Even major numbers are LTS.~~ **Wrong — the ratified policy is odd = LTS.**
</details>
**Board-by-board hardware rollout, decided 2026-08-29 (extends the above as boards come
online).** Real silicon is arriving incrementally (Beelink SER5 in hand now; RasPi 5 + Milk-V
orderable around Mon 2026-08-31), so the hardware release is split per real board in hand,
each its own even-minor cut on the same line (each `X.Y.0` here is an LTS point-in-time cut,
not a separate dev line):
online; corrected 2026-09-22 for the ratified §XXX policy — these are working-line minor
releases, not LTS cuts, since v2.x is the even/working line).** Real silicon is arriving
incrementally (Beelink SER5 in hand now; RasPi 5 + Milk-V orderable around Mon 2026-08-31), so
the hardware release is split per real board in hand, each its own minor cut on the v2.x
working line (v2.1.0, the Tripod/kernel reshuffle, landed first — see the ladder above):
- **v2.2.0 — amd64 bare metal.** Beelink SER5 (in hand). Gate: the generic GPT/FAT32
thumbdrive image (`make -f Makefile.starkernel ARCH=amd64 thumbdrive`) flashes to and boots
+963
View File
@@ -0,0 +1,963 @@
# 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
0. [Fates](#0-fates)
1. [The v4 core ISA](#1-the-v4-core-isa)
2. [Compiler conventions](#2-compiler-conventions)
3. [Decisions](#3-decisions)
4. [Foundation layer (new words)](#4-foundation-layer-new-words)
5. [Fate of every v3 primitive](#5-fate-of-every-v3-primitive)
6. [Messaging between nodes](#6-messaging-between-nodes)
7. [Memory-mapped registers](#7-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 circular hardware stacks, not visible to code (D-2): data stack `T`, `S` + 8 circular (10 deep), return stack `R` + 8 circular (9 deep). No overflow or underflow; pushing past the bottom silently overwrites the oldest entry. |
| 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. |
| **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. | **Moot.** `ROLL` is retired under D-2. |
| **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. |
**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 corrupts silently rather than faulting. 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.
```forth
\ ---- 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` | RET | No visible stack pointer (D-2). |
| `PICK` | RET | No visible stack pointer (D-2). |
| `ROLL` | RET | No visible stack pointer (D-2); D-7 is moot. |
### 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 | `( src dst u -- )`, bytes: `push over over - -if UP drop pop jump CMOVE> UP: drop pop jump CMOVE` — `CMOVE>` when `src < dst`, else `CMOVE`, so overlapping bytes are read before they are overwritten, as v3's `memmove`. The test is the sign of `src - dst`, in line. 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. |
| `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). |
```forth
\ 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 unspecified, as for `UM/MOD` (§4); v3 reported an error and stopped the line. |
| `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 unspecified, as for `/`. |
| `/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. As v3: the number in the current base, then one space; the `.R` words right-justify it in `width` columns first, print a wider number whole, and pad nothing for `width <= 0`. (The space after the `.R` words is not FORTH-79; it is what v3 prints.) 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: the field width of the `.R` words. Like `BASE` and `HLD`, it lives in node memory, so the width is on neither stack while the picture runs. |
| `.S` | RET | No visible stack pointer (D-2). |
| `?` | 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`. |
```forth
\ 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`).
```forth
\ number output. ABS is in line: -if A inv 1 + A:
: .R ( n width -- )
(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 jump SPACE
: . ( n -- ) 0 jump .R
: U.R ( u width -- ) (W) b! !b 0 <# #S #> jump TAIL
: U. ( u -- ) 0 jump U.R
: D.R ( d width -- ) (W) b! !b dup push -if A DNEGATE A: <# #S pop SIGN #> jump TAIL
: D. ( d -- ) 0 jump D.R
```
```forth
\ 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.
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, entered by a jump, and all six share one tail, which ends in a jump
to `SPACE`. 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` |
| `EXPECT` `QUERY` | CC | Built on `KEY` (DEV). |
| `SPAN` `TIB` `>IN` `SOURCE` | CC | Interpreter state on the host node. |
| `WORD` `ENCLOSE` | CC | Parser. |
| `NUMBER` `CONVERT` | CC | Rewritten to honour `BASE` (v3's `NUMBER` is base 10 only). |
| `S"` `(s")` `[']` | CC | Compiler words. |
| `LITERAL` `[LITERAL]` (placeholders) | RET | The working `LITERAL` is in §5.17. |
```forth
\ 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. The address range is not checked (out-of-range addressing is still open, `node.h`). 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`. |
| `."` `(do-string)` | CC | Compiler words. |
### 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. |
| `SP@` `SP!` | RET | No visible stack pointer (D-2). |
### 5.13 Dictionary manipulation
| Word | Fate |
| --- | --- |
| `'` `FIND` `SMUDGE` `HIDDEN` `>BODY` `>NAME` `NAME>` `>LINK` `LINK>` `CFA` `LFA` `NFA` `PFA` `TRAVERSE` `INTERPRET` | CC |
### 5.14 Vocabularies
| Word | Fate |
| --- | --- |
| `VOCABULARY` `DEFINITIONS` `CONTEXT` `CURRENT` `FORTH` `ORDER` `(FIND)` | CC |
### 5.15 System
| Word | Fate | Notes |
| --- | --- | --- |
| `(` `\` | CC | |
| `EXECUTE` | CAP | `push ;` (tail-jumps to the xt; the xt returns to `EXECUTE`'s caller) |
| `NOP` | OP | `nop` |
| `QUIT` `ABORT` `ABORT"` `(ABORT")` `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 |
| --- | --- | --- |
| `:` `;` `CREATE` `VARIABLE` `CONSTANT` `DOES>` `IMMEDIATE` `STATE` `[` `]` `LITERAL` `COMPILE` `[COMPILE]` `FORGET` `FENCE` | CC | |
| `LIT` | OP | `@p` |
| `does_rt` | RET | Internal helper; `DOES>` is implemented by the compiler capsule. |
### 5.18 Control flow
| Word | Fate | v4 definition / notes |
| --- | --- | --- |
| `IF` `ELSE` `THEN` `BEGIN` `UNTIL` `AGAIN` `WHILE` `REPEAT` `DO` `?DO` `LOOP` `+LOOP` `LEAVE` `CASE` `OF` `ENDOF` `ENDCASE` | CC | Compile-time structure words. Expansions per §2 and below. |
| `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 drop pop drop jump PAST` — discards the limit and index and leaves the loop at once. In a single loop this is what v3 does. v3 left the two on its return stack (index set to limit) and jumped past the loop, 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-03) in `LOOP` and `+LOOP`, including sequences recorded from the v3 binary. |
| `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 `SWAP`s 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 `|a| * 2^16 >= |b| * 2^(2N-1)`, possible only for `|b| < 2^17`, where it is `a1 >= b0 << (N-17)` unsigned. `MSB` and `MAXHI` are the high cells of Q min and Q max. Executed on the golden model (2026-10-02) against an independent limb-by-limb reference (including `NODE-ERROR`), on width-native edge values, and against v3's `q48_div` for non-negative `a < 2^48`. Leaves its caller 3 data cells under its arguments and 3 return entries. Clobbers `A` and `B`. |
| `(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.REDUCE) x|`, `1 - xu^2/2! + xu^4/4! - …` to `n = 10`, run by `Q.SIN`'s loop: `(Q.REDUCE) -if A1 inv 1 + A1: 0 (QT) b! !b 0 over 0 Q.* drop (QT) 1 + b! !b 65536 (QT) 2 + b! !b 65536 (QT) 3 + b! !b 2 (QT) 4 + b! !b -1 (QT) 5 + b! !b jump TRIG` — term and sum start at 1.0, `n` at 2, and the sign cell is 0 so nothing is negated at the end. It jumps into the loop rather than calling it, so it costs no extra return entry. Executed on the golden model (2026-10-03): bit for bit v3's result on angles of every size and sign. Leaves its caller 3 data cells under its argument and 2 return entries. Clobbers `A` and `B`. |
| `Q.SIN` | CAP | v3's `q48_sin_approx`: on `r = (Q.REDUCE) x` and `xu = |r|`, `xu - xu^3/3! + xu^5/5! - …` to `n = 11`, each term the last times `xu^2` over `n(n-1)`, stopping once a term is below 10 ulp; negated if `r < 0`. After the reduction every value fits one cell, so the variable `(QT)` holds single cells and the only calls are `(Q.REDUCE)`, `Q.*`, `Q./` and `UM*`. The Taylor loop is written so that `Q.COS` can jump into it. Executed on the golden model (2026-10-03): bit for bit v3's result on angles of every size and sign. Leaves its caller 3 data cells under its argument and 2 return entries. Clobbers `A` and `B`. |
| `(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: `|x| mod 2pi` is two `UM/MOD` steps, the high cell first and its remainder then leading the low cell. `dup (QT) b! !b -if L1 DNEGATE L1: 0 411774 UM/MOD drop 411774 UM/MOD drop (QT) b! @b -if P1 drop inv 1 + jump J1 P1: drop J1: dup -205888 + -if BIG drop dup 205887 + -if OK drop 411774 + ; OK: drop ; BIG: drop -411774 + ;` Executed on the golden model (2026-10-03), bit for bit v3's. Clobbers `A` and `B`. |
| `(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 `|m - e^y| < 100` ulp; result `y + k * ln 2` with `ln 2 = 45426`. `e^y` is `Q.EXP`'s Taylor loop **written in line** (flattened, ruled 2026-10-02), so `Q.LOG` calls only `Q.*`, `Q./` and `UM*` and the call chain is one level shorter than through `Q.EXP`. After the reduction `m`, `y`, the Taylor term and its sum each fit one cell at either width, so the variable `(QL)` holds single cells. `x <= 0` returns 0 and sets `NODE-ERROR` (D-12). Executed on the golden model (2026-10-03): bit for bit v3's result for every `x > 0` tested, including a sweep of the reduced `m`. v3's clamp `y = max(y - corr, 0)` is kept for fidelity but cannot fire: over all 65536 values of `m` it is never reached, and the iteration never needs more than 4 rounds. Leaves its caller 3 data cells under its argument and 2 return entries. Clobbers `A` and `B`. |
| `(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 `|x' - x| < 10` ulp; `sqrt(0) = 0`, `sqrt(1.0) = 1.0`. `q < 0` returns 0 and sets `NODE-ERROR` (D-12). `q`, `x` and the rounds left live in the variable `(QR)`. The halving is a logical double shift, `push a! 0 pop +* MAXHI and push drop a pop`. Executed on the golden model (2026-10-02): bit for bit v3's result for `0 <= q < 2^48`; above that v3's `q48_div` saturates and v4 divides correctly, so they part. Leaves its caller 3 data cells under its argument and 2 return entries. Clobbers `A` and `B`. |
| `(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 = |q|`, stopping once a term is below 50 ulp; `1.0 Q./ e^|q|` for `q < 0`; `e^0 = 1.0`; `|q| >= 16.0` gives 0 (`q < 0`) or **Q max** (v3 returned all ones, which reads as −ulp under D-8). `x`, the term, the sum, the sign and the term index live in the variable `(QE)`, so only `Q.*` and `Q./` arguments sit on the stacks. Executed on the golden model (2026-10-02): bit for bit v3's result for `|q| < 16.0` (at 64-bit cells as the same value, high cell 0). Leaves its caller 3 data cells under its argument and 2 return entries. Clobbers `A` and `B`. |
| `(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 `|q|` shifted right 16 as a double: its low cell by `Q.TO-INT`'s `+*` shift in line, its high cell by `2/` and `HIMASK`, which clears the top 16 bits. Executed on the golden model (2026-10-03) against a C reference, on every seventh fraction, and against a transcript of the v3 binary for non-negative values. Leaves its caller 4 data cells and 3 return entries. Clobbers `A` and `B`. |
| `(QP)` | CAP | Variable, 4 cells: `Q.PRINT`'s saved `BASE`, `|q|` and the sign. |
### 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
```forth
: 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`. |
+257
View File
@@ -0,0 +1,257 @@
# StarForth v4.0.0 — Justification
This document records why StarForth v4.0.0 exists, what it changes, and why each major decision was
made. The specification is `DECOMPOSITION.md`. Per project practice, this document is written before
any v4 code.
---
## 1. The problem with v3
StarForth v3 is a successful software machine. It boots on amd64, aarch64, and riscv64, runs the
Tripod fleet on LithosAnanke, and has held K≡1.0 across 38,400+ experimental runs. But it was designed
for a large host, and it shows:
- **More than 300 C primitives.** Most are not primitive in any hardware sense. Double-cell arithmetic,
string handling, comparisons, pictured output, and Q48.16 transcendentals are all expressible in a
handful of machine operations.
- **64-bit cells and 5 MB of linear memory per VM.** Reasonable on a PC; far too large for a node in a
fabric.
- **Diagnostics of its own implementation.** A significant block of words (hot-words cache statistics,
lookup strategies, pipelining metrics, Bayesian cache models) measures v3's software dictionary, not
the computation the dictionary performs.
- **Hermes in software.** Message routing, per-message ACL checks, and TTL expiry are C code executed by
a CPU that is also doing everything else.
None of this is wrong for a hosted or bare-metal OS. It is wrong for silicon. The project's direction
is now an FPGA embodiment, and eventually an ASIC, and v3 cannot be carried there by porting.
## 2. What v4 is
StarForth v4 is a Forth machine designed to be the same thing in software and in hardware:
1. **A 32-instruction core ISA** derived from Chuck Moore's F18, the node of the GA144. Every core word
is a mnemonic; every mnemonic is one 5-bit opcode.
2. **Everything else is capsule code**, compiled from those 32 instructions, or a message to a node that
owns a service, or a memory-mapped register, or retired.
3. **A mesh of small nodes** that talk to their four neighbours through blocking ports. Hermes becomes
the network itself: routing, ACL checks, and TTL expiry move into logic in every node's router.
4. **Compudynamics as a side effect of execution.** Heat counters, the anti-clock, and the heartbeat are
driven by instruction retirement in hardware. They cost no instructions.
5. **A power-aware governor** built from a multi-level Rolling Window of Truth, controlling timing only.
v4 is a new implementation, not a refactor. v3 remains the reference system for LithosAnanke until v4
reaches parity.
## 3. Why Moore's F18 instruction set
**It is proven minimal.** Moore spent decades removing instructions from his stack machines. The F18's
32 opcodes are the result: enough to build a complete Forth, nothing that can be composed from the
rest. There is no multiply, no divide, no compare, and no `SWAP`; each is a short sequence (for example
`SWAP` is `over push push drop pop pop`).
**It fits a 32-bit word exactly.** 32 opcodes need 5 bits. Six slots fill 30 bits of a 32-bit
instruction word, with 2 spare. One fetch feeds six instructions.
**It matches the project's formal-verification plan.** Proving 32 instruction semantics in Isabelle/HOL
is a bounded task. Proving 300 C primitives is not. Every higher word then inherits correctness from its
definition, which is itself a checkable object.
**It matches the dictionary-shrink plan that was already underway.** The existing POST suite, which
exercises every dictionary word, was to be used as a regression gate while C primitives were replaced by
colon definitions. v4 carries that plan to its end point: the surviving primitives are the ISA.
**It comes with a mesh precedent.** The GA144 places 144 F18 nodes on one die, each talking to its
neighbours through blocking ports. v4 adopts that topology directly.
## 4. Why a mesh, and why Hermes goes into the fabric
v3's Tripod is several VMs sharing one CPU, with Hermes arbitrating messages between them in software.
The mesh replaces time-sharing with space: each VM role runs on its own node or group of nodes,
concurrently.
Moving Hermes into the fabric has three consequences:
- **The message semantics become hardware.** Per-message ACL checks and unconditional TTL expiry, which
v3 already treats as rules rather than options, become router logic that cannot be bypassed.
- **Contention disappears as a scheduling problem.** A blocking port is flow control. There is no
scheduler to write, which honours the existing design goal of avoiding one.
- **The SOS mechanism generalises.** Any node can emit an `SOS` packet. A node whose router fails can
only stop forwarding, which its neighbours detect as blocked ports; this is the hardware form of v3's
"Hermes raises a semaphore while sinking" rule.
## 5. Why cell width becomes a parameter
The Zynq-7000's processing system is a Cortex-A9, a 32-bit ARMv7-A core. Rather than maintain a separate
32-bit fork, v4 makes cell width a build parameter of one VM (32 or 64). This has three benefits:
1. **The ARM becomes a real StarForth host**, not just a bootloader, at 32 bits.
2. **Mesh nodes use 32-bit cells**, roughly halving stack and ALU cost in the fabric.
3. **It opens a second invariance axis.** K≡1.0 has been shown invariant across amd64, aarch64, and
riscv64. If it also holds across cell widths, the conservation law is shown not to depend on word
size either. That is a stronger claim than ISA invariance alone.
The physics does not shrink with the cell. Heat and K arithmetic remain 64-bit (`int64_t` in C99 on
every host; double cells on a 32-bit node). Changing only the payload width keeps the experiment clean:
any difference in K can be attributed to cell width and not to lost precision.
## 6. Why the physics splits into "what" and "when"
The fabric can measure real power: the Zynq's XADC reads on-die temperature and supply voltages, and a
current sensor on the core rail gives true power draw. This makes "heat" a physical quantity rather than
a metaphor.
Physical measurements are noisy and never reproducible run to run. If they controlled which code
executes, parity hashes would break and formal proofs of behaviour would become impossible. v4
therefore splits the physics:
| Layer | Driven by | Controls | Property |
| --- | --- | --- | --- |
| **Virtual heat** | Instruction retirement and call counts | What executes (selection, promotion, eviction) | Deterministic and provable |
| **Physical power** | XADC and rail current | When things happen (clock gating, node sleep, message pacing) | Adaptive, never affects results |
This settles a question left open in the original FPGA concept: whether compudynamic feedback into the
control unit should affect only timing or also the execution path. The answer is both, through separate
channels: logic chooses *what*, physics chooses *when*.
## 7. Why the governor is a multi-level Rolling Window of Truth
The timing governor uses the project's own Rolling Window of Truth mechanism at three timescales:
| Window | Timescale | Governs |
| --- | --- | --- |
| Short | microseconds | Clock gating on one node |
| Medium | milliseconds | Node sleep and wake |
| Long | seconds | Thermal trend and mesh-wide message pacing |
Positive feedback (rising message load) wakes neighbouring nodes and raises the clock. Negative feedback
(rising temperature or power) throttles pacing and puts cool nodes to sleep. Each level reacts much more
slowly than the one below it, so the loops do not fight; hysteresis at each level prevents flapping at
thresholds. Hard limits (thermal ceiling, minimum clock) sit outside the adaptive layer as fixed logic.
A small neural network is a later candidate. Because the governor only controls timing, a poor governor
costs power or speed and never correctness, so it is a safe place to experiment. The DoE recorder
(below) produces exactly the training data such a network would need, so the two approaches can be
compared on identical workloads.
## 8. Division of labour on the Zynq
| Component | Runs on | Role |
| --- | --- | --- |
| Mesh nodes | Fabric | All StarForth execution, the anti-clock, heat counters, routers |
| Governor | Fabric | Multi-level RWT, single clock domain, cycle-exact |
| Host node | ARM (32-bit) | Boot and bitstream load, compiler capsule, console bridge, DoE recorder |
The anti-clock stays in the fabric because it is defined as a pure function of the execution stream and
must live where execution happens. The heartbeat's adaptive loop stays in the fabric because a loop
crossing the PS–PL boundary would inherit ARM-side jitter (caches, interrupts, bus latency).
The ARM's recorder role keeps measurement separate from the thing being measured: the fabric pushes
DoE rows into a FIFO, the ARM drains them to storage, and if the ARM falls behind rows are dropped
rather than execution stalled. This is the fabric form of the planned `HB-ON`/`HB-OFF` disk recording.
## 9. Why the compiler lives on the host node
GA144 nodes have 64 words of RAM and 64 of ROM, and arrayForth compiles on a host. v4 follows the same
split. The outer interpreter, dictionary, vocabularies, and defining words form the compiler capsule,
which runs on the host node. Mesh nodes receive compiled code. Large capsules stay in DDR and are
streamed to nodes as needed, so capsule size is not limited by node memory.
This is also why so many v3 words become CC rather than CAP in `DECOMPOSITION.md`: they are compiler
machinery, not computation.
## 10. Development path
Each stage is checked against the one before it. Nothing proceeds on trust.
1. **Hosted golden model.** A C99 implementation of the v4 ISA and node model, with cell width, node
count, and node memory as parameters. The POST suite, rewritten against v4 capsules, must pass at
both 32 and 64 bits, and K≡1.0 must hold.
2. **Hosted mesh.** Several golden-model nodes wired through simulated ports, running the Tripod roles
as nodes. The 144-node configuration is exercised here, since the host is not limited by fabric size.
3. **Co-simulation.** The node RTL is compiled with Verilator and run in lockstep with the golden model.
After every instruction, stacks, registers, and heat counters are compared. The first mismatch
identifies the faulty mnemonic exactly.
4. **FPGA.** A 2×2 mesh on the PZ7020, then the largest grid that fits. The bitstream only has to match
the co-simulation.
5. **ASIC.** A single v4 node, not the mesh, as a proof of silicon through an open-source shuttle
(currently Tiny Tapeout on IHP's SG13G2 130 nm open PDK). The same RTL is reused; block RAM is
replaced by the process's SRAM macros.
## 11. Scaling beyond the PZ7020
Node count, node memory, and cell width are parameters, and the mesh is generated by a loop over rows
and columns, so a larger board changes numbers, not design.
| Part | Approximate resources | Estimated nodes |
| --- | --- | --- |
| Zynq-7020 | ~53K LUTs, 140 BRAM36 | ~8–16 |
| Zynq-7045 | ~218K LUTs, 545 BRAM36 | ~50–70 |
| Zynq UltraScale+ (e.g. Kria K26) | ~117K LUTs, 144 BRAM36, 64 UltraRAM | ~30–40, with much larger node memory |
| Larger UltraScale+ / Versal | Several hundred K LUTs and up | A full 144 |
Node counts are estimates. The first hardware measurement to take is the LUT cost of one node plus its
router on the 7020; every other board's capacity follows from that number.
UltraScale+ parts also change the host: their Cortex-A53 cores are aarch64, so the host node can run
64-bit StarForth while the mesh runs 32-bit cells, which the cell-width parameter already supports.
Larger meshes will need registered router-to-router links to close timing, and the free edition of
Vivado supports only smaller devices, so tool licensing must be checked before choosing a board.
## 12. Risks
| Risk | Mitigation |
| --- | --- |
| Capsule-level arithmetic is much slower than v3's C primitives on a hosted build. | Accepted. v4's measure of performance is the fabric, where each instruction is one cycle. The hosted build is a correctness oracle. |
| Word addressing makes byte and string operations expensive. | D-1 in `DECOMPOSITION.md` keeps the choice open; colorForth's packed, pre-parsed source is a proven alternative for text. |
| K≡1.0 may behave differently at 32-bit cell width. | That is an experimental result either way, and the hosted golden model finds it before any hardware exists. |
| Hand-traced definitions contain errors. | Every CAP definition is a POST target against the v3 C primitive it replaces. |
| The mesh does not fit the 7020 at a useful size. | Measure one node first; the design scales to larger parts unchanged. |
## 13. Relationship to intellectual property
v4 strengthens rather than replaces the existing claims. The Jacquard Selector, the Rolling Window of
Truth, and the Steady State Machine all survive, now as hardware structures. The new elements a filing
could draw on are: compudynamic heat as a zero-cost side effect of instruction retirement; the split of
deterministic virtual heat (selection) from physical power (timing); per-packet ACL and TTL enforcement
in a mesh router; and conservation invariance across cell width. Whether any of these belong in the
LithosAnanke filing is a question for counsel.
## 14. Definition of done for v4.0.0
- The 32-instruction ISA is specified, with every open decision in `DECOMPOSITION.md` §3 settled.
- The hosted golden model passes the rewritten POST suite at 32-bit and 64-bit cell widths.
- K≡1.0 holds on the golden model at both widths, on all three host ISAs.
- A hosted mesh runs the Tripod roles as nodes, with Hermes as the network.
- Verilator co-simulation of one node matches the golden model instruction for instruction.
## 15. Products
Ruled by Captain Bob, 2026-10-03. v4 turns one project into three products built on the same engine:
1. **Hosted StarForth F18.** The v4 engine as a native Linux build for amd64, arm64 and riscv64, on
real hardware.
2. **FPGA StarForth F18.** A 32-bit build of the engine loaded into the FPGA. This is the gateway into
building the whole system, and its foundation.
3. **StarshipOS.** The bare-metal product: LithosAnanke running the v4 F18 engine.
The FORTH-79 vocabulary, recomposed on the F18 engine (`DECOMPOSITION.md`), is stored as a capsule. The
StarshipOS-specific portions are recomposed and stored as capsules in the same way. The source tree
will be reorganised around this split.
## 16. Acceptance criteria
Ruled by Captain Bob, 2026-10-03. Until then v4 had none: this document and `v4/README.md` named a
POST suite and K≡1.0 on three host ISAs, neither of which exists yet, so nothing could be called
accepted.
- **v4 is equivalent to v3 at any point in time.** It must provide the same vocabularies and behave
the same way. The only difference is the machine underneath: the F18-derived engine instead of the
original StarForth VM.
- **Every ISA, hosted and bare metal, must still reach its `ok` prompt.** That is amd64, aarch64 and
riscv64, as a hosted build and as a bare-metal boot.
The existing acceptance tests keep their force and their rules: the three-architecture QEMU boot for
anything bare metal (`.claude/CLAUDE.md`), and the hosted three-architecture test
(`docs/lithosananke/hosted-acceptance-test/README.md`). Passing `make -C v4 test` is a development
check on the golden model, not acceptance.
+10 -4
View File
@@ -137,16 +137,22 @@ before building) rather than wiring it into `doe.4th`.
## `.4th` File Structure
Every `.4th` file must follow StarForth's block format. The block system
maps source text to 1024-byte logical blocks; the `Block NNNN` header tells
the loader which block slot to fill.
maps source text to logical blocks; the `Block NNNN` header tells the loader
which block slot to fill.
**Mandatory rules:**
1. The first line of each logical block must be `Block NNNN` (capital B,
single space, decimal integer).
2. Block numbers must be unique within a single capsule file.
2. Block numbers must be unique within a single capsule file, and must fall
in `[2048, 5120)`.
3. Blocks are loaded in file order and executed top-to-bottom.
4. Each block can hold up to 1024 bytes of source text.
4. Each block can hold at most 16 content lines, each at most 64 characters
long (`validate_forth_blocks` in `tools/mkcapsule.c`, corrected 2026-09-19
against the tool itself — **not** a 1024-byte budget: 8 lines of 128
characters is 1024 bytes and still fails, since the limit is enforced
per-line and per-line-count, not as a total byte count). Verify with
`mkcapsule --lint capsules/`, not `wc -c`.
5. Comments use `( ... )` — parentheses with spaces inside.
6. Word definitions use `: NAME ... ;` — standard FORTH-79.

Some files were not shown because too many files have changed in this diff Show More