docs: reorganise -- current reference gathered, finished work archived, superseded and dead ideas marked as such

docs/CLAUDE.md Phase 2, 2026-10-14.  All moves by git mv; every archived
file carries a header saying what it is, why, and where to look instead.

- docs/reference/ (new): roadmap and versioning policy, M7.1, word-ACL
  design, kernel args, hosted acceptance test, dictionary, primitives,
  ontology, L8 table -- previously spread over docs/, docs/lithosananke/
  and docs/03-architecture/.
- docs/working/archive/historical/: finished work (session plan, test-gap
  audit, birthing plan, fix write-ups, the January architecture draft).
- docs/working/archive/superseded/: the Hermes-VM Tripod docs and July
  Hermes designs (-> FABRIC-3.5/3.6), the StarForth roadmap, every working
  doc already flagged superseded or obsolete.
- docs/working/archive/dead/: the hal_* target contract (never adopted),
  L4Re, metrics-and-knobs.
- Papers and proposals into docs/working/papers/.
- References rewritten across the repo (not logs/, not docs/patent/);
  CLAUDE.md's documentation section rewritten.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-14 16:03:56 -04:00
co-authored by Claude Opus 5.5
parent 1a4b163861
commit 7aed588f78
116 changed files with 305 additions and 175 deletions
+726
View File
@@ -0,0 +1,726 @@
# LithosAnanke / StarForth — Word Dictionary Reference
**Status:** Living reference document, generated 2026-08-12. Keep it up to date as words
are added, removed, or re-registered — see "Keeping this current" at the bottom.
**Scope: core C primitives only.** This covers every word registered via `register_word()`
(and the one file that uses `vm_create_word()` directly, `physics_pipelining_diagnostic_words.c`)
across `src/word_source/*.c` and `src/starkernel/capsule/mama_forth_words.c` — roughly 470
words. It does **not** cover the ~330 FORTH-defined words living inside `.4th` capsules
(`ACL.4th`, `zuse.4th`, `doe.4th`, workload capsules, etc.) — those are experiment/workload/
policy definitions layered on top of this vocabulary, not the language itself, and change
per-capsule rather than per-build. If a capsule-word reference is wanted later, it belongs in
a separate document — the two have very different lifecycles.
**Source of truth.** Every entry below was extracted directly from source: the word name as
passed to `register_word()`, the stack effect and description from the doc comment
immediately above the implementing function (or inferred from the function body where no
comment exists, marked `(inferred)`). Registration order within each file is preserved
(rather than alphabetized) because related words are grouped together at the call site.
**Two name collisions exist in the live dictionary.** FORTH dictionary lookup walks the
definition chain newest-first (`vm->latest` backward via `->link`), so when two files
register the same name, **the file registered later in `register_forth79_words()`
(`src/word_registry.c`) wins** — the earlier registration becomes permanently unreachable by
name (though still present in the chain). Both cases are noted at point of use below:
- `[`, `]`, `STATE` — registered by `dictionary_manipulation_words.c` (Module 13), then again
by `defining_words.c` (Module 17). **`defining_words.c`'s versions are live.**
- `MOD`, `/MOD`, `*/`, `*/MOD` — registered by `arithmetic_words.c` (Module 4), then again by
`mixed_arithmetic_words.c` (Module 6). **`mixed_arithmetic_words.c`'s versions are live.**
---
## Stack (`stack_words.c`)
| Word | Stack Effect | Description |
|---|---|---|
| `DROP` | `( n -- )` | Removes the top stack item. |
| `DUP` | `( n -- n n )` | Duplicates the top stack item. |
| `?DUP` | `( n -- n n \| n -- 0 )` | Duplicates the top item only if it is non-zero. |
| `SWAP` | `( n1 n2 -- n2 n1 )` | Exchanges the top two stack items. |
| `OVER` | `( n1 n2 -- n1 n2 n1 )` | Copies the second stack item to the top. |
| `ROT` | `( n1 n2 n3 -- n2 n3 n1 )` | Rotates the top three stack items, moving the third to the top. |
| `-ROT` | `( n1 n2 n3 -- n3 n1 n2 )` | Reverse-rotates the top three stack items. |
| `DEPTH` | `( -- n )` | Pushes the current number of items on the data stack. |
| `PICK` | `( n -- stack[n] )` | Copies the nth stack item (0-indexed from top, after popping `n`) to the top. |
| `ROLL` | `( n -- )` | Moves the nth stack item to the top, shifting intermediate items down. |
## Return Stack (`return_stack_words.c`)
FORTH-79 compliance, stated in this file's own header: only `>R`/`R>`/`R@` are provided — no
direct return-stack addressing (`RP!`/`RP@`), which FORTH-79 forbids.
| Word | Stack Effect | Description |
|---|---|---|
| `>R` | `( x -- )` | Moves x from the data stack to the return stack. |
| `R>` | `( -- x )` | Moves x from the return stack back to the data stack. |
| `R@` | `( -- x )` | Copies the top of the return stack to the data stack without removing it. |
## Memory (`memory_words.c`)
| Word | Stack Effect | Description |
|---|---|---|
| `@` | `( addr -- n )` | Fetches a cell from VM memory at `addr`. |
| `!` | `( n addr -- )` | Stores cell `n` at VM memory address `addr`. |
| `C@` | `( addr -- c )` | Fetches a byte from VM memory at `addr`. |
| `C!` | `( c addr -- )` | Stores byte `c` at VM memory address `addr`. |
| `+!` | `( n addr -- )` | Adds `n` to the cell currently at `addr`. |
| `-!` | `( n addr -- )` | Subtracts `n` from the cell currently at `addr`. |
| `2@` | `( addr -- x_low x_high )` | Fetches two consecutive cells starting at `addr`. |
| `2!` | `( x_low x_high addr -- )` | Stores two consecutive cells starting at `addr`. |
| `FILL` | `( addr len c -- )` | Fills `len` bytes starting at `addr` with byte value `c`. |
| `MOVE` | `( addr1 addr2 len -- )` | Copies `len` bytes from `addr1` to `addr2`, handling overlap correctly. |
| `ERASE` | `( addr len -- )` | Zeroes `len` bytes starting at `addr`. |
| `CELLS` | `( n -- n' )` | Multiplies `n` by the cell size in bytes. |
## Arithmetic (`arithmetic_words.c`)
`MOD`, `/MOD`, `*/`, `*/MOD` below are **shadowed** — see the collision note at the top of
this document; `mixed_arithmetic_words.c`'s versions are the ones actually live.
| Word | Stack Effect | Description |
|---|---|---|
| `+` | `( n1 n2 -- n3 )` | Adds `n1` and `n2`. |
| `-` | `( n1 n2 -- n3 )` | Subtracts `n2` from `n1`. |
| `*` | `( n1 n2 -- n3 )` | Multiplies `n1` by `n2`. |
| `/` | `( n1 n2 -- n3 )` | Divides `n1` by `n2`; errors on division by zero. |
| `MOD` *(shadowed)* | `( n1 n2 -- n3 )` | `n1` modulo `n2`; errors on division by zero. |
| `/MOD` *(shadowed)* | `( n1 n2 -- n3 n4 )` | Pushes `n1 MOD n2` (remainder) then `n1 / n2` (quotient). |
| `*/` *(shadowed)* | `( n1 n2 n3 -- n4 )` | Computes `n1 * n2 / n3` using a 64-bit intermediate to avoid overflow. |
| `*/MOD` *(shadowed)* | `( n1 n2 n3 -- n4 n5 )` | Computes `n1 * n2 / n3`, pushing remainder then quotient, via a 64-bit intermediate. |
| `1+` | `( n -- n+1 )` | Adds 1. |
| `1-` | `( n -- n-1 )` | Subtracts 1. |
| `2+` | `( n -- n+2 )` | Adds 2. |
| `2-` | `( n -- n-2 )` | Subtracts 2. |
| `2*` | `( n -- n*2 )` | Multiplies by 2 (left shift). |
| `2/` | `( n -- n/2 )` | Divides by 2 (right shift). |
| `ABS` | `( n -- \|n\| )` | Absolute value. |
| `NEGATE` | `( n -- -n )` | Two's-complement negation. |
| `MIN` | `( n1 n2 -- n3 )` | Pushes the smaller of `n1` and `n2`. |
| `MAX` | `( n1 n2 -- n3 )` | Pushes the larger of `n1` and `n2`. |
## Logical & Comparison (`logical_words.c`)
| Word | Stack Effect | Description |
|---|---|---|
| `AND` | `( n1 n2 -- n3 )` | Bitwise AND. |
| `OR` | `( n1 n2 -- n3 )` | Bitwise OR. |
| `XOR` | `( n1 n2 -- n3 )` | Bitwise XOR. |
| `NOT` | `( flag -- flag )` | FORTH-79 logical NOT: 0 → true (-1), non-zero → false (0). |
| `INVERT` | `( n1 -- n2 )` | Bitwise complement (FORTH-83 extension). |
| `LSHIFT` | `( x1 u -- x2 )` | Logical left shift by `u` bits. |
| `RSHIFT` | `( x1 u -- x2 )` | Logical right shift by `u` bits. |
| `0=` | `( n -- flag )` | True if `n` is zero. |
| `0<` | `( n -- flag )` | True if `n` is negative. |
| `0>` | `( n -- flag )` | True if `n` is positive. |
| `0<>` | `( n -- flag )` | True if `n` is non-zero. |
| `=` | `( n1 n2 -- flag )` | True if equal. |
| `<>` | `( n1 n2 -- flag )` | True if not equal. |
| `<` | `( n1 n2 -- flag )` | True if `n1 < n2` (signed). |
| `>` | `( n1 n2 -- flag )` | True if `n1 > n2` (signed). |
| `>=` | `( n1 n2 -- flag )` | True if `n1 >= n2` (signed). |
| `<=` | `( n1 n2 -- flag )` | True if `n1 <= n2` (signed). |
| `U<` | `( u1 u2 -- flag )` | True if `u1 < u2` (unsigned). |
| `U>` | `( u1 u2 -- flag )` | True if `u1 > u2` (unsigned). |
| `WITHIN` | `( n low high -- flag )` | True if `low <= n < high`. |
| `TRUE` | `( -- flag )` | Pushes -1. |
| `FALSE` | `( -- flag )` | Pushes 0. |
## Mixed Arithmetic (`mixed_arithmetic_words.c`)
`MOD`, `/MOD`, `*/`, `*/MOD` here are the **live** versions — see the collision note at the
top of this document.
| Word | Stack Effect | Description |
|---|---|---|
| `M+` | `( d_high d_low n -- d_high' d_low' )` | Adds single-cell signed `n` to double-cell `d`, with carry propagation. |
| `M-` | `( d_high d_low n -- d_high' d_low' )` | Subtracts single-cell signed `n` from double-cell `d`, with carry propagation. |
| `M*` | `( n1 n2 -- d_low d_high )` | Multiplies two single-cell signed integers into a double-cell result (TOS = high cell). |
| `M/MOD` | `( d_high d_low n -- remainder quotient )` | Divides double-cell dividend `d` by single-cell divisor `n`; TOS = quotient. |
| `MOD` | `( n1 n2 -- remainder )` | Computes `n1 % n2`; errors on division by zero. |
| `/MOD` | `( n1 n2 -- remainder quotient )` | Divides `n1` by `n2`, leaving both remainder and quotient (TOS = quotient). |
| `*/` | `( n1 n2 n3 -- quotient )` | Computes `(n1 * n2) / n3` using a wider intermediate to avoid overflow. |
| `*/MOD` | `( n1 n2 n3 -- remainder quotient )` | Computes `(n1 * n2) / n3`, leaving both remainder and quotient. |
## Double-Precision (`double_words.c`)
A double is represented on the stack as `dlow dhigh` (high cell on top).
| Word | Stack Effect | Description |
|---|---|---|
| `S>D` | `( n -- d )` | Converts a single-precision number to double, sign-extending into the high cell. |
| `D+` | `( d1 d2 -- d3 )` | Double-precision addition with carry propagation. |
| `D-` | `( d1 d2 -- d3 )` | Double-precision subtraction with borrow propagation. |
| `DNEGATE` | `( d1 -- d2 )` | Double-precision two's-complement negation. |
| `DABS` | `( d1 -- d2 )` | Double-precision absolute value. |
| `DMAX` | `( d1 d2 -- d3 )` | Maximum of two double-precision numbers. |
| `DMIN` | `( d1 d2 -- d3 )` | Minimum of two double-precision numbers. |
| `D<` | `( d1 d2 -- flag )` | True if `d1 < d2` (double-precision signed compare). |
| `D=` | `( d1 d2 -- flag )` | True if `d1 = d2`. |
| `2DROP` | `( d -- )` | Drops a double (two cells). |
| `2DUP` | `( d -- d d )` | Duplicates a double. |
| `2SWAP` | `( d1 d2 -- d2 d1 )` | Swaps two doubles. |
| `2OVER` | `( d1 d2 -- d1 d2 d1 )` | Copies the second double to the top. |
| `2ROT` | `( d1 d2 d3 -- d2 d3 d1 )` | Rotates three doubles. |
| `2>R` | `( d -- ) ( R: -- d )` | Moves a double to the return stack (nesting-aware: preserves the resume IP above it when inside a colon-word executor). |
| `2R>` | `( -- d ) ( R: d -- )` | Moves a double from the return stack back to the data stack. |
| `2R@` | `( -- d ) ( R: d -- d )` | Copies a double from the return stack without removing it. |
| `D0=` | `( d -- flag )` | True if the double is zero. |
| `D0<` | `( d -- flag )` | True if the double is negative. |
| `D2*` | `( d1 -- d2 )` | Double-precision left shift by 1 (multiply by 2), carrying across cells. |
| `D2/` | `( d1 -- d2 )` | Double-precision arithmetic right shift by 1 (divide by 2), preserving sign, carrying across cells. |
## Format & Numeric Output (`format_words.c`)
| Word | Stack Effect | Description |
|---|---|---|
| `.` | `( n -- )` | Prints `n` in the current base, followed by a space. |
| `.R` | `( n width -- )` | Prints `n` right-justified in a field of `width` characters. |
| `U.` | `( u -- )` | Prints `u` as unsigned, followed by a space. |
| `U.R` | `( u width -- )` | Prints `u` as unsigned, right-justified in a field of `width`. |
| `D.` | `( d -- )` | Prints double-cell `d` as signed decimal (or `DOUBLE-OVERFLOW` if it doesn't fit a single cell). |
| `D.R` | `( d width -- )` | Prints double-cell `d` right-justified in a field of `width`. |
| `.S` | `( -- )` | Prints the entire data stack contents non-destructively, prefixed with the depth. |
| `?` | `( addr -- )` | Fetches and prints the cell at `addr`. |
| `DUMP` | `( addr u -- )` | Prints a hex/ASCII memory dump of `u` bytes starting at `addr`. |
| `<#` | `( -- )` | Begins pictured numeric output conversion; resets the hold buffer. |
| `#` | `( ud \| n -- ud2 )` | Converts one digit of the number (in the current base) into the hold buffer. |
| `#S` | `( ud \| n -- 0 0 )` | Converts all remaining digits of the number into the hold buffer. |
| `#>` | `( [ud] -- addr u )` | Ends pictured numeric conversion; pushes the address and length of the formatted string. |
| `HOLD` | `( c -- )` | Prepends character `c` to the pictured-numeric hold buffer. |
| `SIGN` | `( n -- )` | If `n` is negative, holds a `-` character for pictured output. |
| `BASE` | `( -- addr )` | Pushes the VM address of the `BASE` variable (current numeric conversion radix). |
| `DECIMAL` | `( -- )` | Sets `BASE` to 10. |
| `HEX` | `( -- )` | Sets `BASE` to 16. |
| `OCTAL` | `( -- )` | Sets `BASE` to 8. |
## String & Text Processing (`string_words.c`)
| Word | Stack Effect | Description |
|---|---|---|
| `COUNT` | `( addr1 -- addr2 u )` | Converts a counted string (length-prefixed) at `addr1` into an address/length pair. |
| `EXPECT` | `( addr u -- )` | Reads a line from stdin into the buffer at `addr` (max `u` bytes) and updates `SPAN` with the actual length read. |
| `SPAN` | `( -- addr )` | Pushes the address of the `SPAN` variable, holding the character count from the last `EXPECT`/`QUERY`. |
| `QUERY` | `( -- )` | Reads a line from stdin into the terminal input buffer (TIB), resetting `>IN` to 0 and updating `SPAN`. |
| `TIB` | `( -- addr )` | Pushes the address of the terminal input buffer. |
| `WORD` | `( c -- addr )` | Parses the next word from the input stream delimited by character `c`, returning the address of a counted-string scratch buffer. |
| `(s")` | `( -- c-addr u )` | Runtime for compiled `S"` — reads an inline length-prefixed string from the threaded-code stream and pushes its address/length, advancing past it. |
| `S"` | `( "ccc<quote>" -- c-addr u )` | Parses a quoted string literal. Interpret mode: stores it at `HERE` and pushes address/length. Compile mode: compiles `(s")` plus the inline string data. Immediate. |
| `>IN` | `( -- addr )` | Pushes the address of the `>IN` input-stream-position variable. |
| `SOURCE` | `( -- addr u )` | Pushes the address and length of the current input source (the TIB). |
| `BL` | `( -- c )` | Pushes the ASCII code for space (32). |
| `[']` | `( -- xt )` | Parses the next word from the input stream and pushes (or compiles as a literal) its execution token. Immediate. |
| `LITERAL` | `( -- )` | Placeholder/no-op in this implementation. |
| `[LITERAL]` | `( -- )` | Placeholder/no-op in this implementation (delegates to `LITERAL`). |
| `CONVERT` | `( d1 addr1 -- d2 addr2 )` | Converts digit characters starting at `addr1` into a running double-precision accumulator `d1`→`d2` (simplified, base 10 only), returning the address just past the converted digits. |
| `NUMBER` | `( addr -- n flag )` | Converts a counted string at `addr` to a number (base 10 only); flag is 1 on success, 0 on failure. |
| `ENCLOSE` | `( addr c -- addr1 n1 n2 n3 )` | Parses a delimited field: `n1` = leading-delimiter count, `n2` = end offset of the field, `n3` = offset past trailing delimiters. |
| `-TRAILING` | `( addr u -- addr u' )` | Trims trailing ASCII spaces from a string (direct or counted-string form auto-detected), returning the trimmed length. |
| `CMOVE` | `( addr1 addr2 u -- )` | Copies `u` bytes from `addr1` to `addr2`, ascending address order (correct for overlap when `addr2 >= addr1`). |
| `CMOVE>` | `( addr1 addr2 u -- )` | Copies `u` bytes from `addr1` to `addr2`, descending address order (correct for overlap when `addr2 < addr1`). |
| `COMPARE` | `( addr1 u1 addr2 u2 -- n )` | Lexicographically compares two strings (direct or counted-string form), returns -1/0/+1. |
| `SEARCH` | `( addr1 u1 addr2 u2 -- addr3 u3 flag )` | Finds the first occurrence of string 2 within string 1; returns the matching tail and a found flag. |
| `SCAN` | `( addr u char -- addr' u' )` | Finds the first occurrence of `char` in the string, returning the tail starting there (or end-of-string if not found). |
| `SKIP` | `( addr u char -- addr' u' )` | Skips leading occurrences of `char` in the string, returning the remaining tail. |
| `BLANK` | `( addr u -- )` | Fills `u` bytes at `addr` with ASCII space (auto-detects counted-string form). |
## I/O & Terminal (`io_words.c`)
| Word | Stack Effect | Description |
|---|---|---|
| `EMIT` | `( c -- )` | Outputs the character `c` to the terminal. |
| `CR` | `( -- )` | Outputs a newline character. |
| `KEY` | `( -- c )` | Reads one character from the terminal and pushes it. |
| `?TERMINAL` | `( -- flag )` | Pushes whether input is available (stub implementation, always pushes false). |
| `TYPE` | `( addr u -- )` | Outputs `u` characters from VM memory starting at `addr`. |
| `SPACE` | `( -- )` | Outputs a single space character. |
| `SPACES` | `( n -- )` | Outputs `n` space characters. |
| `(do-string)` | `( -- )` | Runtime helper compiled by `."` — reads an inline length-prefixed string from the threaded code and prints it, advancing IP past it. Not meant to be called directly. |
| `."` | `( "ccc<quote>" -- )` | Immediate. Interpretation: parses and prints a string up to the closing `"` directly. Compilation: compiles `(do-string)` plus the inline string data. |
## Block & Mass Storage (`block_words.c`)
| Word | Stack Effect | Description |
|---|---|---|
| `BLOCK` | `( u -- addr )` | Returns the VM address of block `u`'s content, loading it if needed (does not mark dirty). |
| `BUFFER` | `( u -- addr )` | Returns the VM address of block `u`'s buffer without reading its content; marks it dirty. |
| `UPDATE` | `( -- )` | Marks the currently-loaded block (per `SCR`) dirty and syncs it to the underlying block-subsystem buffer. |
| `BLK-CONFIRM-FORMAT` | `( lbn -- )` | Commits the low-level disk container format for the slot owning block `lbn`; until called, writes to that disk slot are refused. Must be called only by the disk's owner after classifying its content as safe. |
| `SAVE-BUFFERS` | `( -- )` | Writes back every dirty block window slot to the block subsystem. |
| `EMPTY-BUFFERS` | `( -- )` | Invalidates all block window slots and zeroes all user blocks, without writing back. |
| `FLUSH` | `( -- )` | Same as `SAVE-BUFFERS` — syncs and invalidates all buffers. |
| `LOAD` | `( u -- )` | Sets `SCR` to `u` and interprets block `u`'s content as FORTH source. |
| `LIST` | `( u -- )` | Sets `SCR` to `u` and prints the block's content formatted as 16 lines of 64 characters with line numbers. |
| `THRU` | `( u1 u2 -- )` | Executes `LOAD` on every block from `u1` to `u2` inclusive. |
| `SCR` | `( -- addr )` | Pushes the VM address of the `SCR` (current block) variable. |
| `-->` | `( -- )` | Continues interpretation on the next sequential block after the current `SCR`. |
## Dictionary & Compilation (`dictionary_words.c`)
| Word | Stack Effect | Description |
|---|---|---|
| `HERE` | `( -- addr )` | Returns the dictionary pointer. |
| `ALIGN` | `( -- )` | Aligns the dictionary pointer to a cell boundary. |
| `ALLOT` | `( n -- )` | Allocates `n` bytes in the dictionary; `n` may be negative to deallocate. |
| `,` | `( n -- )` | Compiles a single cell value into the dictionary at `HERE`. |
| `C,` | `( c -- )` | Compiles a single byte value into the dictionary at `HERE`. |
| `2,` | `( d -- )` | Compiles a double-cell value into the dictionary at `HERE`, low cell first. |
| `PAD` | `( -- addr )` | Returns the VM address of the 512-byte scratch text buffer at top of memory. |
| `SP!` | `( sp -- )` | Sets the data stack pointer; can only shrink the stack, never grow it. |
| `SP@` | `( -- sp )` | Returns the current data stack-pointer index (top is 0). |
| `LATEST` | `( -- addr )` | Returns the VM address near the most recent compiled definition (end of dictionary). |
## Dictionary Manipulation (`dictionary_manipulation_words.c`)
`[`, `]`, `STATE` below are **shadowed** — see the collision note at the top of this
document; `defining_words.c`'s versions are the ones actually live.
| Word | Stack Effect | Description |
|---|---|---|
| `[` *(shadowed)* | `( -- )` | Enters interpretation mode. |
| `]` *(shadowed)* | `( -- )` | Enters compilation mode. |
| `STATE` *(shadowed)* | `( -- addr )` | Pushes the address of this file's own `state_variable` (a separate static, not the VM's `vm->state_addr` used by `defining_words.c`'s `STATE`). |
| `SMUDGE` | `( -- )` | Compile-only: intended to toggle the hidden/smudge bit of the latest word (function body is a stub/placeholder in the current source). |
| `HIDDEN` | `( -- )` | Compile-only: sets the hidden flag on the most recently defined word. |
| `>BODY` | `( xt -- addr )` | Converts an execution token to its data-field (body) address. |
| `>NAME` | `( xt -- addr )` | Converts an execution token to the address of its name field. |
| `NAME>` | `( addr -- xt )` | Converts a name-field address back to its execution token, by linear search of the dictionary. |
| `>LINK` | `( addr -- addr )` | Pushes the address of a dictionary entry's link field. |
| `LINK>` | `( addr -- addr )` | Dereferences a link-field address to get the next dictionary entry. |
| `CFA` | `( addr -- xt )` | Returns the code-field address of an entry — in this implementation, the entry pointer itself. |
| `LFA` | `( addr -- addr )` | Alias for `>LINK` — returns the link-field address. |
| `NFA` | `( addr -- addr )` | Returns the name-field address of a dictionary entry. |
| `PFA` | `( addr -- addr )` | Returns the parameter-field (body) address of a dictionary entry. |
| `TRAVERSE` | `( addr n -- addr )` | Moves forward or backward across a name field by `n`. |
| `INTERPRET` | `( -- )` | Sets interpret mode; actual interpretation is driven by the VM's own `vm_interpret()`, not this word's body. |
| `FIND` | `( "name" -- xt \| 0 )` | Parses the next word and pushes its dictionary entry pointer, or 0 if not found (a miss is not an error). |
| `'` | `( "name" -- xt )` | FORTH-79 tick: parses the next word and pushes its execution token; errors if not found. Non-immediate — the compiler compiles a call to it in compile mode. |
## Vocabulary System (`vocabulary_words.c`)
| Word | Stack Effect | Description |
|---|---|---|
| `VOCABULARY` | `( -- )` | Creates a new vocabulary; executing the created word selects itself as CONTEXT. |
| `DEFINITIONS` | `( -- )` | Sets CURRENT to CONTEXT, so new words are defined into the CONTEXT vocabulary. |
| `CONTEXT` | `( -- addr )` | Returns the VM address of the cell holding the CONTEXT vocabulary pointer. |
| `CURRENT` | `( -- addr )` | Returns the VM address of the cell holding the CURRENT vocabulary pointer. |
| `FORTH` | `( -- )` | Makes FORTH the CONTEXT vocabulary. |
| `ORDER` | `( -- )` | Prints the current search order (CONTEXT then FORTH) and CURRENT. |
| `(FIND)` | `( addr -- addr flag )` | Primitive finder: searches CONTEXT then FORTH vocabularies for the counted string at `addr`. |
## System & Environment (`system_words.c`)
| Word | Stack Effect | Description |
|---|---|---|
| `(` | `( -- )` | Begins a comment; skips input up to the matching `)`, honoring nested parens. Immediate. |
| `\` | `( -- )` | Line comment; skips input to the end of the current line. Immediate. |
| `COLD` | `( -- )` | Performs a cold start: resets VM state and rewinds `HERE` toward the base dictionary. |
| `WARM` | `( -- )` | Performs a warm restart: resets stacks/mode/error without rewinding `HERE`. |
| `BYE` | `( -- )` | Halts the VM, signalling the REPL to stop. (Overridden for Hera — see Kernel: Capsule Birth & Tripod Fleet below.) |
| `REBOOT` | `( addr len -- )` | Sets boot arguments from the given string and performs a cold reset (kernel target) or prints a hosted-stub message and halts. Interpret-only. |
| `SAVE-SYSTEM` | `( -- )` | Writes a trivial snapshot of VM memory (up to `HERE`) to `forth_system.img`. |
| `WORDS` | `( -- )` | Lists every word name currently in the dictionary, 8 per line, with a total count. |
| `VLIST` | `( -- )` | Lists every dictionary entry with its name, address, and flags byte. |
| `SEE` | `( "name" -- )` | Decompiles and prints the named word's definition (threaded code, or `<primitive>` for C words). |
| `PAGE` | `( -- )` | Clears the terminal screen via an ANSI escape sequence. |
| `EXECUTE` | `( xt -- )` | Executes the word whose execution token is on the stack. |
| `NOP` | `( -- )` | Does nothing. |
| `79-STANDARD` | `( -- flag )` | Prints and pushes whether FORTH-79 standard-compliance mode is active (-1) or not (0). |
| `QUIT` | `( -- )` | Clears the return stack and returns to the interpreter, without treating it as an error. Immediate. |
| `ABORT` | `( -- )` | Clears both stacks and returns to the interpreter; not treated as an error condition. |
| `(ABORT")` | `( flag addr len -- )` | Runtime helper for `ABORT"`: if `flag` is nonzero, prints the message at `addr len` and performs `ABORT` semantics. |
| `ABORT"` | `( flag -- )` | Parses a message up to the next `"`; if `flag` is nonzero (interpret mode) or at run time (compiled), prints it and aborts. Immediate. |
## Line Editor (`editor_words.c`)
Non-standard (not FORTH-79).
| Word | Stack Effect | Description |
|---|---|---|
| `L` | `( u -- )` | Prints line `u` (0-15) of the current SCR block. |
| `S` | `( c-addr len u -- )` | Sets line `u` of the current SCR block from a buffer, padding with spaces to 64 chars. |
| `SHOW` | `( -- )` | Prints the whole 16x64 current screen with line numbers. |
| `EDIT` | `( u -- )` | Enters a tiny interactive line-editor shell (stdin/stdout) on block `u`. |
## Defining Words (`defining_words.c`)
`[`, `]`, `STATE` here are the **live** versions — see the collision note at the top of this
document.
| Word | Stack Effect | Description |
|---|---|---|
| `:` | `( "name" -- )` | Begins a colon definition; parses the name and enters compile mode. Immediate. |
| `;` | `( -- )` | Ends a colon definition, compiling an implicit `EXIT` and returning to interpret mode. Immediate. |
| `CREATE` | `( "name" -- )` | Defines a new word whose runtime pushes its data-field address (FORTH-79: allocates no data itself). |
| `VARIABLE` | `( "name" -- )` | Defines a new word that allocates one cell (initialized to 0) and whose runtime pushes that cell's address. |
| `CONSTANT` | `( n "name" -- )` | Defines a new word whose runtime pushes the fixed value `n`. |
| `IMMEDIATE` | `( -- )` | Marks the most recently defined word as IMMEDIATE (executes during compilation instead of being compiled in). |
| `STATE` | `( -- addr )` | Pushes the VM address of the `STATE` variable (0 = interpreting, nonzero = compiling). |
| `[` | `( -- )` | Enters interpret state (sets `STATE` to 0). Immediate. |
| `]` | `( -- )` | Enters compile state (sets `STATE` to -1). |
| `FORGET` | `( "name" -- )` | Removes the named word and every word defined after it from the dictionary, rewinding `HERE`. |
| `COMPILE` | `( "word" -- )` | Legacy immediate word: parses the next word and compiles a call to it. Immediate. |
| `[COMPILE]` | `( "word" -- )` | Compiles the next word even if it is itself IMMEDIATE. Immediate. |
| `LIT` | `( -- n )` | Runtime primitive: fetches the next cell from threaded code (via the return-stack IP) and pushes it as a literal. |
| `LITERAL` | `( n -- )` | Compiles `n` as a literal into the current definition (via `LIT`). Immediate. |
| `does_rt` | `( -- )` *(inferred)* | Internal helper: patches the just-created child word to use the `DOES>` runtime and records the `DOES>` body address in its parameter field. Not meant to be called directly from FORTH. |
| `DOES>` | `( -- )` | Finalizes a defining word's CREATE-part and compiles the following code as the DOES>-body run by every word the defining word subsequently creates. Immediate, compile-only. |
| `DEFER` | `( "name" -- )` | Creates a deferred word with an empty execution-token slot; errors if executed before `IS` sets it. |
| `IS` | `( xt "name" -- )` | Stores execution token `xt` into a word previously created with `DEFER`. |
| `DEFER@` | `( "name" -- xt )` | Fetches the execution token currently stored in a deferred word. |
## Control Flow (`control_words.c`)
| Word | Stack Effect | Description |
|---|---|---|
| `(BRANCH)` | `( -- )` | Runtime: unconditional branch — reads a relative byte offset from the inline thread and adjusts the return-stack IP by it. |
| `(0BRANCH)` | `( f -- )` | Runtime: conditional branch — pops a flag; if zero, branches by the inline relative offset, otherwise skips it. |
| `(?DO)` | `( limit index -- )` | Runtime for `?DO` — if index equals limit the loop body is skipped entirely (branches past it); otherwise pushes (limit, index) onto the return stack as the loop frame. |
| `(DO)` | `( limit index -- )` | Runtime for `DO` — unconditionally pushes (limit, index) onto the return stack as the loop frame. |
| `(LOOP)` | `( -- )` | Runtime for `LOOP` — increments the loop index; if still less than the limit, branches back to the loop body, else drops the loop frame and falls through. |
| `(+LOOP)` | `( n -- )` | Runtime for `+LOOP` — adds `n` to the loop index; continues looping while the (signed-direction-aware) index hasn't crossed the limit, else exits. |
| `(LEAVE)` | `( -- )` | Runtime for `LEAVE` — forces the current loop to exit at the next `LOOP`/`+LOOP` by setting index equal to limit. |
| `IF` | `( f -- )` *(compile-time)* | Compiles a conditional branch (`0BRANCH`) with a placeholder target, pushed for later patching by `ELSE`/`THEN`. Immediate. |
| `ELSE` | `( -- )` *(compile-time)* | Patches the matching `IF`'s branch to land here, compiles an unconditional branch past the else-clause, pushed for `THEN` to patch. Immediate. |
| `THEN` | `( -- )` *(compile-time)* | Patches the matching `IF` or `ELSE` branch to land here, closing the conditional. Immediate. |
| `BEGIN` | `( -- )` *(compile-time)* | Marks the start of a loop (target for `UNTIL`/`AGAIN`/`REPEAT`). Immediate. |
| `WHILE` | `( f -- )` *(compile-time)* | Compiles a conditional exit branch inside a `BEGIN...REPEAT` loop, patched by `REPEAT`. Immediate. |
| `REPEAT` | `( -- )` *(compile-time)* | Compiles an unconditional branch back to `BEGIN` and patches `WHILE`'s exit branch to land after it. Immediate. |
| `AGAIN` | `( -- )` *(compile-time)* | Compiles an unconditional branch back to the matching `BEGIN` (infinite loop unless exited via `LEAVE`/`EXIT`). Immediate. |
| `UNTIL` | `( f -- )` *(compile-time)* | Compiles a conditional branch back to the matching `BEGIN`; loop repeats while the flag is false. Immediate. |
| `?DO` | `( limit index -- )` *(compile-time)* | Compiles the `(?DO)` runtime call plus loop-frame bookkeeping for `LOOP`/`LEAVE`. Immediate. |
| `DO` | `( limit index -- )` *(compile-time)* | Compiles the `(DO)` runtime call plus loop-frame bookkeeping for `LOOP`/`LEAVE`. Immediate. |
| `LOOP` | `( -- )` *(compile-time)* | Compiles the `(LOOP)` runtime call, patches the back-edge to `DO`/`?DO` and any pending `LEAVE` sites. Immediate. |
| `+LOOP` | `( n -- )` *(compile-time)* | Compiles the `(+LOOP)` runtime call, patches the back-edge to `DO`/`?DO` and any pending `LEAVE` sites. Immediate. |
| `LEAVE` | `( -- )` *(compile-time)* | Compiles the runtime leave-flag plus a forward branch out of the enclosing loop, patched at the next `LOOP`/`+LOOP`. Immediate. |
| `I` | `( -- i )` | Pushes the index of the innermost active `DO`/`?DO` loop. |
| `J` | `( -- j )` | Pushes the index of the next-outer `DO`/`?DO` loop (requires nested loops). |
| `UNLOOP` | `( -- )` | Discards the current loop's (limit, index) frame from the return stack without branching — used before an early `EXIT` from inside a loop. |
| `EXIT` | `( -- )` | Marks the current colon definition for a one-shot early return. |
| `CASE` | `( n -- n )` *(compile-time)* | Marks the start of a `CASE...OF...ENDOF...ENDCASE` selector statement. Immediate. |
| `OF` | `( n1 n2 -- \| n1 )` *(compile-time)* | Compiles a compare-and-branch (`OVER = 0BRANCH DROP`) for one `CASE` clause. Immediate. |
| `ENDOF` | `( -- )` *(compile-time)* | Ends one `OF` clause: patches its branch, compiles a jump to `ENDCASE`. Immediate. |
| `ENDCASE` | `( n -- )` *(compile-time)* | Ends the `CASE` statement: compiles a `DROP` for the selector and patches all `ENDOF` branches to land here. Immediate. |
## StarForth Extensions (`starforth_words.c`)
Each word is registered twice — once into the root vocabulary, once again into the
`STARFORTH` vocabulary context (`ENTROPY@`/`ENTROPY!` only registered the second time).
Listed once below.
| Word | Stack Effect | Description |
|---|---|---|
| `ENTROPY@` | `( addr -- n )` | Fetches the execution-heat counter for the `DictEntry` at `addr` (validated against the live dictionary chain). |
| `ENTROPY!` | `( n addr -- )` | Sets the execution-heat counter for the `DictEntry` at `addr`. |
| `WORD-ENTROPY` | `( -- )` | Prints execution-heat statistics (per word, total, average) for every word in the dictionary. |
| `RESET-ENTROPY` | `( -- )` | Resets every word's execution-heat counter and compudynamics metadata (temperature, latency, last-active) to zero. |
| `TOP-WORDS` | `( n -- )` | Prints the `n` most frequently executed words, sorted descending by execution heat. |
| `(-` | `( -- )` | Comment word: consumes input up to a balanced closing `)` — used for `init.4th` metadata markers. |
| `INIT` | `( -- )` | Reads `./capsules/core/init.4th`, remaps and copies its numbered blocks sequentially starting at block 1, executes them via `LOAD`, then zeroes the init blocks for userspace reuse. |
| `VERSION` | `( -- )` | Prints the StarForth version string (`STARFORTH_VERSION_FULL`). |
| `SEED` | `( n -- )` | Seeds the internal PRNG (LCG) with `n` (forces non-zero state). |
| `RANDOM` | `( lo hi -- n )` | Returns a pseudo-random integer in the inclusive range `[lo, hi]`. |
| `WAIT` | `( n -- )` | Waits `n` heartbeat ticks (calls `vm_tick()` n times) — architecture-independent, no wall-clock dependency. |
| `ZUSE-AUTHENTICATE` | `( -- )` | Sets `vm->zuse_session = 1` — C-only superuser bypass, no FORTH-level authentication logic. |
## Word-Level ACL (`acl_words.c`)
| Word | Stack Effect | Description |
|---|---|---|
| `ACL-MODE@` | `( xt -- mode )` | Fetches the ACL enforcement mode of the word `xt`: 0 = STRICT (permanent), 1 = TTL (expires after countdown). |
| `ACL-PINNED?` | `( xt -- flag )` | Pushes -1 if the word `xt` is pinned (its ACL fields are immutable), 0 otherwise. |
| `ACL-TTL@` | `( xt -- n )` | Fetches the current TTL countdown of the word's ACL entry. |
| `ACL-ALLOW@` | `( xt -- flag )` | Fetches the cached allow/deny decision for the word: -1 = allowed, 0 = denied. |
| `ACL-HEAT@` | `( xt -- heat )` | Fetches the execution-heat counter for the word (used to calibrate TTL). |
| `ACL-WORD-ID` | `( xt -- n )` | Fetches the word's stable `word_id`, assigned at registration and usable as a persistent table index. |
| `ACL-MODE!` | `( mode xt -- )` | Sets the ACL enforcement mode of word `xt`. No-op if the word is pinned. |
| `ACL-TTL!` | `( n xt -- )` | Sets the TTL countdown on the word's ACL entry (clamped to `[0, UINT32_MAX]`). No-op if pinned. |
| `ACL-ALLOW!` | `( flag xt -- )` | Sets the cached allow/deny decision for word `xt`. No-op if pinned. |
| `ACL-PIN` | `( xt -- )` | Sets `acl_pinned` on word `xt`. One-way ratchet — can never be cleared once set. |
| `ACL-INHERIT` | `( src dst -- )` | Copies `acl_mode` from `src` to `dst`, then clears `dst`'s pin, resets its TTL to 0, and sets `acl_allow` to 1 (optimistic default). |
| `ACL-INIT-PRIMITIVES` | `( -- )` | Walks the entire dictionary and resets ACL fields (TTL=0, allow=1, mode=TTL) on every unpinned entry. |
## Compudynamics: Benchmark (`physics_benchmark_words.c`)
| Word | Stack Effect | Description |
|---|---|---|
| `BENCH-DICT-LOOKUP` | `( iterations -- )` | Benchmarks dictionary lookup performance over a set of common words for the given iteration count; warns if iterations < 10,000 for statistical validity. |
| `PHYSICS-CACHE-STATS` | `( -- )` | Displays detailed hot-words cache statistics and current cache contents. |
| `PHYSICS-TOGGLE-CACHE` | `( -- )` | Enables/disables the hot-words cache at runtime for A/B testing. |
| `PHYSICS-RESET-STATS` | `( -- )` | Resets cache and pipeline-metrics statistics for a clean before/after comparison. |
| `PHYSICS-BUILD-INFO` | `( -- )` | Prints the current hot-words cache build configuration and runtime state. |
| `PHYSICS-BAYESIAN-REPORT` | `( addr_baseline -- )` | Generates a Bayesian inference report comparing current cache stats against a baseline. |
## Compudynamics: Pipelining Diagnostics (`physics_pipelining_diagnostic_words.c`)
Registers via `vm_create_word()` directly rather than `register_word()` — the only file in
the tree that does this (verified by a `vm_create_word(vm, "` sweep of the whole
`word_source/` directory).
| Word | Stack Effect | Description |
|---|---|---|
| `PIPELINING-SHOW-STATS` | `( c-addr u -- )` | Displays word-to-word transition metrics (prefetch attempts/hits/misses, latency saved) for the named word. |
| `PIPELINING-SHOW-TOP-TRANSITIONS` | `( c-addr u n -- )` | Displays the top `n` words that typically follow the named word, ranked by transition count. |
| `PIPELINING-RESET-ALL` | `( -- )` | Clears transition metrics from every word in the dictionary. |
| `PIPELINING-ENABLE` | `( -- )` | Reports whether pipelining metrics collection is enabled (compile-time flag; this word does not toggle it). |
| `PIPELINING-STATS` | `( -- )` | Displays aggregate pipelining statistics (transitions, prefetch hit rate) across the whole dictionary. |
| `PIPELINING-ANALYZE-WORD` | `( c-addr u -- )` | Comprehensive analysis of one word's transition predictability (entropy/distribution spread) plus prefetch net benefit. |
## Compudynamics: Freeze/Decay Control (`physics_freeze_words.c`)
| Word | Stack Effect | Description |
|---|---|---|
| `FREEZE-WORD` | `( caddr u -- )` | Sets `WORD_FROZEN` on the named word so its execution heat stops decaying; silently succeeds if not found. |
| `UNFREEZE-WORD` | `( caddr u -- )` | Clears `WORD_FROZEN` on the named word, letting heat decay resume normally. |
| `FROZEN?` | `( caddr u -- flag )` | Pushes true (-1) if the named word is frozen, false (0) otherwise or if not found. |
| `HEAT!` | `( heat caddr u -- )` | Directly writes `execution_heat` for the named word (diagnostics; bypasses normal accumulation/decay). |
| `HEAT@` | `( caddr u -- heat )` | Pushes the current `execution_heat` of the named word (0 if not found). |
| `SHOW-HEAT` | `( caddr u -- )` | Prints the named word's heat plus frozen/pinned status to stdout. |
| `ALL-HEATS` | `( -- )` | Prints a table of every dictionary word sorted by descending execution heat. |
| `DECAY-RATE@` | `( -- rate )` | Pushes the compile-time base heat decay rate (Q48.16) per microsecond used by Loop #3. |
| `FREEZE-CRITICAL` | `( -- )` | Freezes a hard-coded list of 21 system-critical words (`DUP`, `DROP`, `SWAP`, `IF`, `EXECUTE`, etc.) so they never decay out of the hot-words cache. |
## Compudynamics: General Diagnostics (`physics_diagnostic_words.c`)
| Word | Stack Effect | Description |
|---|---|---|
| `PHYSICS-WORD-METRICS` | `( -- )` | Displays temperature, execution heat, latency, mass, and derived thermal pressure for the most recently executed word. |
| `PHYSICS-CALC-KNOBS` | `( -- )` | Calculates and displays recommended priority/sampling/stack-limit/affinity adjustments based on the most recent word's thermal pressure. |
| `PHYSICS-BURN` | `( n -- )` | Executes the most recently executed word `n` times, printing thermal feedback progress and a final summary. |
| `PHYSICS-SHOW-FEEDBACK` | `( -- )` | Displays the full feedback-loop demonstration (metrics → math → knobs → effect) for the most recent word. |
## Dictionary Heat Optimization (`dictionary_heat_diagnostic_words.c`)
| Word | Stack Effect | Description |
|---|---|---|
| `HEAT-PERCENTILES` | `( -- 25th 50th 75th )` | Pushes the three heat percentile thresholds used by heat-aware dictionary lookup. |
| `LOOKUP-STRATEGY@` | `( -- strategy )` | Pushes the current lookup strategy: 0 = naive (newest-first), 1 = heat-aware. |
| `LOOKUP-STRATEGY!` | `( strategy -- )` | Sets the lookup strategy; only 0 or 1 accepted, other values silently discarded. |
| `REORG-BUCKETS` | `( -- )` | Forces an immediate re-sort of dictionary lookup buckets by heat and refreshes percentile thresholds. |
| `SHOW-HEAT-OPTIMIZATION` | `( -- )` | Prints a summary of lookup strategy, percentile thresholds, and resulting hot/warm/cool zones. |
| `COMPARE-LOOKUPS` | `( iterations -- )` | Micro-benchmarks naive vs. heat-aware dictionary lookup over six common words and prints the speedup/slowdown. |
## Log Level Control (`log_words.c`)
| Word | Stack Effect | Description |
|---|---|---|
| `LOG-ERROR` | `( -- n )` | Pushes the `LOG_ERROR` level constant. |
| `LOG-WARN` | `( -- n )` | Pushes the `LOG_WARN` level constant. |
| `LOG-INFO` | `( -- n )` | Pushes the `LOG_INFO` level constant. |
| `LOG-TEST` | `( -- n )` | Pushes the `LOG_TEST` level constant. |
| `LOG-DEBUG` | `( -- n )` | Pushes the `LOG_DEBUG` level constant. |
| `LOG-LEVEL!` | `( n -- )` | Sets the active log filter level (clamped to `[LOG_ERROR, LOG_DEBUG]`). |
| `LOG-LEVEL@` | `( -- n )` | Pushes the current log filter level. |
| `(do-log-error)` | `( -- )` | Runtime for compiled `LOG-ERROR"` — emits the inline string at ERROR level. |
| `(do-log-warn)` | `( -- )` | Runtime for compiled `LOG-WARN"` — emits the inline string at WARN level. |
| `(do-log-info)` | `( -- )` | Runtime for compiled `LOG-INFO"` — emits the inline string at INFO level. |
| `(do-log-test)` | `( -- )` | Runtime for compiled `LOG-TEST"` — emits the inline string at TEST level. |
| `(do-log-debug)` | `( -- )` | Runtime for compiled `LOG-DEBUG"` — emits the inline string at DEBUG level. |
| `LOG-ERROR"` | `( "ccc<quote>" -- )` | Logs a quoted string literal at ERROR level (interpret: immediate; compile: compiles the runtime + inline data). Immediate. |
| `LOG-WARN"` | `( "ccc<quote>" -- )` | Logs a quoted string literal at WARN level. Immediate. |
| `LOG-INFO"` | `( "ccc<quote>" -- )` | Logs a quoted string literal at INFO level. Immediate. |
| `LOG-TEST"` | `( "ccc<quote>" -- )` | Logs a quoted string literal at TEST level. Immediate. |
| `LOG-DEBUG"` | `( "ccc<quote>" -- )` | Logs a quoted string literal at DEBUG level. Immediate. |
| `LOG-ERROR-STR` | `( c-addr u -- )` | Logs a stack-supplied string (e.g. from `S"`) at ERROR level. |
| `LOG-WARN-STR` | `( c-addr u -- )` | Logs a stack-supplied string at WARN level. |
| `LOG-INFO-STR` | `( c-addr u -- )` | Logs a stack-supplied string at INFO level. |
| `LOG-TEST-STR` | `( c-addr u -- )` | Logs a stack-supplied string at TEST level. |
| `LOG-DEBUG-STR` | `( c-addr u -- )` | Logs a stack-supplied string at DEBUG level. |
## Q48.16 Fixed-Point Math (`q48_words.c`)
`cell_t` reinterpreted as `uint64_t`; 1.0 = 65536, resolution ≈ 0.0000153.
| Word | Stack Effect | Description |
|---|---|---|
| `Q.+` | `( q1 q2 -- q_sum )` | Q48.16 addition. |
| `Q.-` | `( q1 q2 -- q_diff )` | Q48.16 subtraction. |
| `Q.*` | `( q1 q2 -- q_prod )` | Q48.16 multiplication. |
| `Q./` | `( q1 q2 -- q_quot )` | Q48.16 division; returns `q=0` on division by zero. |
| `Q.ABS` | `( q -- \|q\| )` | Absolute value. |
| `Q.NEG` | `( q -- -q )` | Negation. |
| `Q.LOG` | `( q -- ln_q )` | Natural log via Newton-Raphson approximation. |
| `Q.EXP` | `( q -- e^q )` | Exponential via Taylor series approximation. |
| `Q.SQRT` | `( q -- sqrt_q )` | Square root via Newton-Raphson approximation. |
| `Q.SIN` | `( q -- sin_q )` | Sine (radians, any magnitude) via Taylor series. |
| `Q.COS` | `( q -- cos_q )` | Cosine (radians, any magnitude) via Taylor series. |
| `Q.FROM-INT` | `( n -- q )` | Converts integer to Q48.16 (`n << 16`). |
| `Q.TO-INT` | `( q -- n )` | Converts Q48.16 to integer, truncating (`q >> 16`). |
| `Q.1` | `( -- 65536 )` | Pushes 1.0 in Q48.16. |
| `Q.0` | `( -- 0 )` | Pushes 0.0 in Q48.16. |
| `Q.SCALE` | `( -- 65536 )` | Pushes the scale factor (alias of `Q.1`). |
| `Q.=` | `( q1 q2 -- flag )` | -1 if equal, else 0. |
| `Q.<` | `( q1 q2 -- flag )` | -1 if `q1 < q2`, else 0. |
| `Q.>` | `( q1 q2 -- flag )` | -1 if `q1 > q2`, else 0. |
| `Q.0=` | `( q -- flag )` | -1 if `q = 0`, else 0. |
| `Q.MAX` | `( q1 q2 -- q_max )` | Maximum of two Q48.16 values. |
| `Q.MIN` | `( q1 q2 -- q_min )` | Minimum of two Q48.16 values. |
| `Q.PRINT` | `( q -- )` | Prints as `"integer.frac"` to the console. |
Note: `q48_16_words.c` (despite the similar filename) registers no FORTH words at all — it's
a pure C support library (multiply/divide with wide intermediates) consumed by other files
like `ttf_words.c`, not a word-registration file.
## SSM Inference + Jacquard (`inference_words.c`)
| Word | Stack Effect | Description |
|---|---|---|
| `Q.VARIANCE` | `( addr u -- q )` | Computes the variance of `u` uint64 cells starting at `addr`, result as Q48.16. |
| `INFER-DECAY-SLOPE` | `( addr u -- q )` | Computes the exponential decay slope of `u` cells at `addr` via linear regression, as Q48.16. |
| `INFER-WINDOW-WIDTH` | `( addr u -- n )` | Computes the optimal window width from the variance inflection point of `u` cells at `addr`. |
| `WINDOW-DIVERSITY` | `( -- u )` | Pushes the rolling window's diversity (entropy) metric. |
| `INFER-RUN` | `( -- )` | Runs the full inference engine against this VM's rolling window and dictionary heat stats, updating `vm->last_inference_outputs`. |
| `INFER-WINDOW@` | `( -- u )` | Fetches the last computed adaptive window width. |
| `INFER-DECAY@` | `( -- q )` | Fetches the last computed adaptive decay slope (Q48.16). |
| `INFER-VARIANCE@` | `( -- q )` | Fetches the last computed window variance (Q48.16). |
| `INFER-FIT@` | `( -- q )` | Fetches the last computed slope fit quality (Q48.16). |
| `INFER-EARLY-EXIT@` | `( -- flag )` | Pushes 1 if the last `INFER-RUN` used the ANOVA early-exit path. |
| `L8-MODE` | `( -- n )` | Pushes the current L8 Jacquard mode (0-15). |
| `L8-UPDATE` | `( entropy_q cv_q temporal_q stability_q -- )` | Feeds four Q48.16 metrics into the L8 Jacquard mode selector's update function. |
| `L8-APPLY` | `( -- )` | Applies the current L8 mode to `vm->ssm_config` (legacy 16-mode path). |
| `L8-TABLE-FORCE` | `( config_idx -- )` | Forces the adaptive 128-config table onto `config_idx`, as if the bandit's own selection had picked it, and applies it immediately. |
| `BAYES-CACHE-MEAN` | `( -- q )` | Pushes the Bayesian mean latency estimate for hot-word cache hits (Q48.16). |
| `BAYES-CACHE-LOWER` | `( -- q )` | Pushes the 95% credible lower bound for cache-hit latency. |
| `BAYES-CACHE-UPPER` | `( -- q )` | Pushes the 95% credible upper bound for cache-hit latency. |
| `BAYES-BUCKET-MEAN` | `( -- q )` | Pushes the Bayesian mean latency estimate for dictionary bucket searches. |
| `BAYES-BUCKET-LOWER` | `( -- q )` | Pushes the 95% credible lower bound for bucket-search latency. |
| `BAYES-BUCKET-UPPER` | `( -- q )` | Pushes the 95% credible upper bound for bucket-search latency. |
## DEFER / Late Binding (`defer_words.c`)
| Word | Stack Effect | Description |
|---|---|---|
| `DEFER` | `( -- )` | Parses a name and creates a deferred word with an unset (0) data field; calling it before `IS` sets `vm->error`. |
| `IS` | `( xt -- )` | Parses a name (which must have been created by `DEFER`) and stores `xt` as its target action, enabling late binding. |
| `DEFER@` | `( -- xt )` | Parses a name (must be `DEFER`-created) and pushes its current target execution token. |
## Console Fabric — Framebuffer (`framebuffer_words.c`)
Kernel-only (`__STARKERNEL__`); no-ops on hosted builds. Raw hardware-boundary access —
FABRIC-0.md item 4.3.3.
| Word | Stack Effect | Description |
|---|---|---|
| `PLOT` | `( x y color -- )` | Raw raster pixel write at (x,y), top-left origin, Y-down. No Cartesian awareness. |
| `FB-WIDTH` | `( -- n )` | Pushes the framebuffer width in pixels (0 on hosted builds). |
| `FB-HEIGHT` | `( -- n )` | Pushes the framebuffer height in pixels (0 on hosted builds). |
## Console Fabric — Keyboard (`keyboard_words.c`)
| Word | Stack Effect | Description |
|---|---|---|
| `KBD-SCAN` | `( -- c -1 \| 0 )` | Pops one raw scancode off the amd64 i8042 ring buffer if available; pushes 0 if empty or on other architectures. |
| `KBD-DEBUG` | `( -- isr_count spurious_count )` | Pushes the i8042 interrupt and spurious-interrupt counters (amd64 diagnostic). |
| `VKBD-EVENT` | `( -- code value -1 \| 0 )` | Pops one decoded EV_KEY event off the virtio-input ring buffer (riscv64/aarch64); pushes 0 if empty. |
| `VKBD-DEBUG` | `( -- isr_count )` | Pushes the virtio-input interrupt counter (riscv64/aarch64 diagnostic). |
| `KEY-EVENT` | `( -- keycode pressed -1 \| 0 )` | Cross-architecture converged key event: pops one keycode/pressed pair in the Linux input-keycode namespace, translated from whichever backend (i8042 or virtio-input) is live. |
| `ALT+TAB` | `( -- )` | Programmatic equivalent of the physical Alt+Tab interception — toggles the framebuffer graphics/text overlay state. |
## Console Fabric — TrueType Text (`ttf_words.c`)
Kernel-only (`__STARKERNEL__`); no-op on hosted builds. FABRIC-0.md item 4.3.7e.
| Word | Stack Effect | Description |
|---|---|---|
| `TTF-TEXT` | `( c-addr u x y size color -- )` | Renders a UTF-8 string via the TrueType rasterizer at Cartesian position (x,y) (origin bottom-left of the framebuffer) with the given point size and color. |
## Console Fabric — Scrollback (`scroll_words.c`)
Kernel-only (`__STARKERNEL__`); no-op on hosted builds. FABRIC-0.md item 4.4q (boot-mode
scrollback added by 4.4ac).
| Word | Stack Effect | Description |
|---|---|---|
| `SCROLL-BACK` | `( n -- )` | Moves the REPL framebuffer scrollback view back `n` lines. |
| `SCROLL-FWD` | `( n -- )` | Moves the REPL framebuffer scrollback view forward `n` lines, toward live. |
## Kernel: Capsule Birth & Tripod Fleet (`mama_forth_words.c`)
Kernel-only (`__STARKERNEL__`). Implements the MAMA vocabulary — capsule birth protocol and
Tripod fleet control (`src/starkernel/capsule/mama_forth_words.c`). Two registration
functions exist: `register_mama_forth_words()` gives Hera (Mama) her full word set,
registered identically into both the FORTH and MAMA vocabularies; `register_child_vm_words()`
gives child VMs (Hermes/Artemis/etc.) a smaller subset, since they aren't bootstrapped
through the same path.
**Hera's word set:**
| Word | Stack Effect | Description |
|---|---|---|
| `BYE` | `( -- )` | Hera-only override: reaps all child VMs, then cold-restarts the machine. (Children instead get the standard `system_word_bye` from `system_words.c`, which just halts back to the parent's REPL.) |
| `CONNECT-HERMES` | `( -- )` | Enters Hermes's REPL, birthing her first if not already live/stopped (idempotent). |
| `CONNECT-ARTEMIS` | `( -- )` | Enters Artemis's REPL, birthing her first if not already live/stopped (idempotent). |
| `BIRTH` | `( c-addr u -- )` | Births a named child VM from its capsule (name → `name:init.4th`). Idempotent if already live; refuses to re-birth Hera. |
| `KILL` | `( c-addr u -- )` | Destroys a named VM unconditionally, fanning its heat out to survivors first. Hera cannot be killed. Idempotent. |
| `START` | `( c-addr u -- )` | Enters a named VM's REPL loop synchronously; caller blocks until the target halts via STOP/BYE. Cannot start a LIVE/DEAD/STILLBORN VM. |
| `STOP` | `( -- )` | Self-stop: sets `vm->halted` so the current VM's REPL loop exits on its next iteration. Registered in every VM including children. |
| `USE` | `( c-addr u -- )` | Redirects system-wide REPL input to a named VM without touching the C call stack; `USE Hera` resets to default dispatch. |
| `EXEC` | `( c-addr u -- )` | Executes a named capsule in the current VM — same path as Mama's own init auto-run. |
| `CAPSULE-COUNT` | `( -- n )` | Pushes the number of capsules in the capsule directory. |
| `CAPSULE@` | `( idx -- desc )` | Pushes the capsule descriptor address at index `idx` (0 if out of bounds). |
| `CAPSULE-HASH@` | `( desc -- hash )` | Pushes the content hash field from a capsule descriptor. |
| `CAPSULE-FLAGS@` | `( desc -- flags )` | Pushes the flags field from a capsule descriptor. |
| `CAPSULE-LEN@` | `( desc -- len )` | Pushes the payload length field from a capsule descriptor. |
| `CAPSULE-BIRTH` | `( capsule-id -- vm-id-hi vm-id-lo )` | Births a baby VM from a production (p) capsule by index; returns the new VM's 128-bit ID as a double, or all-ones on failure. |
| `CAPSULE-RUN` | `( capsule-id -- )` | Runs an experiment (e) capsule on Mama by index. |
| `MAMA-VM-ID` | `( -- 0 0 )` | Pushes Mama's (Hera's) own VM ID as a double — always zero. |
| `VM-COUNT` | `( -- n )` | Pushes the number of registered VMs. |
| `VM-CONSERVED?` | `( -- flag )` | FORTH boolean: true if the fleet heat sum is within epsilon of `Q.1` (conservation check). |
| `VM-PHYSICS-STATUS` | `( -- )` | Prints the fleet compudynamics diagnostic report. |
| `VM-STEP` | `( c-addr u -- )` | Gives one REPL turn (prompt, read one line, execute, return) to a named VM — the Compudynamics context-switch primitive. |
| `VM-EXEC` | `( cmd-caddr cmd-u vm-name-caddr vm-name-u -- )` | Injects and immediately executes a command string in a named VM, no readline/blocking. |
| `VM-CALL` | `( cmd-caddr cmd-u vm-name-caddr vm-name-u -- n )` | Like `VM-EXEC` but pops one cell from the target's stack afterward and pushes it onto the caller's stack (cross-VM query primitive). |
| `CAPSULE-TEST` | `( -- )` | Prints a diagnostic message confirming the capsule system is active. |
**Additional words in `register_child_vm_words()` only (not in Hera's own dictionary — this
keeps Hera's `dict_hash` off item 4.1's baseline):**
| Word | Stack Effect | Description |
|---|---|---|
| `STADIUM-ADMIT` | `( identity heat behaviour -- cell \| -1 )` | Admits a mass-1 patron into the calling VM's own Stadium quota; -1 if behaviour tag is invalid or admission is refused. |
| `STADIUM-EVICT` | `( cell -- flag )` | Reaps the patron header at `cell`; true (-1) on success, false (0) if refused (out of range, not resident, pinned, or contains-gated). |
| `STADIUM-RES@` | `( -- heat )` | Read-only peek at the calling VM's own reservoir balance (Q48.16). |
| `STADIUM-RES-PULL` | `( qty -- heat )` | Pulls up to `qty` (Q48.16) from the calling VM's own reservoir; returns the amount actually pulled (never more, never negative). |
| `STADIUM-RES-PUSH` | `( heat -- )` | Credits heat (Q48.16) back into the calling VM's own reservoir. |
| `STADIUM-HEAT@` | `( cell -- heat )` | Reads a resident cell's own heat; 0 if not resident or not owned by the calling VM. |
| `STADIUM-HEAT!` | `( new-heat cell -- )` | Writes a resident cell's own heat, atomically reconciling the reservoir delta; silently refused if not owned by the calling VM or reservoir can't cover an increase. |
| `STADIUM-WORD-HEAT` | `( -- heat )` | Sum of heat held by the calling VM's own word-execution residents (item 4.1's cells) — closes the term application-level conservation checks (e.g. Hermes's `HERMES-K`) need. |
## Hosted-Build-Only Lifecycle Stubs (`lifecycle_words_hosted.c`)
Compiled only when `__STARKERNEL__` is **not** defined. In the hosted build there is no
capsule blob or kernel VM registry, so these five just consume their arguments and log
intent — real behavior lives in `mama_forth_words.c` (`BIRTH`/`USE`) plus separate
kernel-side `KILL`/`PAUSE`/`RESUME` words for the kernel build.
| Word | Stack Effect | Description |
|---|---|---|
| `BIRTH` | `( c-addr u -- )` | Hosted stub: logs "BIRTH name (hosted)"; no real VM lifecycle in the hosted build. |
| `KILL` | `( c-addr u -- )` | Hosted stub: logs "KILL name (hosted)". |
| `PAUSE` | `( c-addr u -- )` | Hosted stub: logs "PAUSE name (hosted)". |
| `RESUME` | `( c-addr u -- )` | Hosted stub: logs "RESUME name (hosted)". |
| `USE` | `( c-addr u -- )` | Hosted stub: logs "USE name (hosted)". |
---
## Keeping this current
This document was built by reading every `register_word()` (and the one `vm_create_word()`)
call site directly, plus the doc comment above each implementing function. When words are
added, removed, or re-registered:
1. Update the relevant category table above — keep registration order within a file, not
alphabetical.
2. If a new file registers a name that collides with an existing one, add it to the collision
note at the top of this document and mark both entries (`*(shadowed)*` / live) at point of
use, the same way `[`/`]`/`STATE` and `MOD`/`/MOD`/`*/`/`*/MOD` are handled above —
determine the winner from `register_forth79_words()`'s call order in `src/word_registry.c`
(later registration wins; search is newest-first via `vm->latest`/`->link`).
2. If a new `word_source/*.c` file is added, give it its own `##` section, placed near
related categories.
3. Word counts aren't tracked as a running total in this document on purpose — they drift
too easily and the categories are the useful unit, not a single number. If a total is
needed, `grep -rc 'register_word(' src/word_source/*.c src/starkernel/capsule/mama_forth_words.c`
plus a check for `vm_create_word(vm, "` (the one alternate registration path found so
far) gets close, but always verify against this document rather than trusting either
grep alone — see the `physics_pipelining_diagnostic_words.c` case above for why.
+745
View File
@@ -0,0 +1,745 @@
# L8 Jacquard: Adaptive 128-Config Heat-Ranked Selector
**Status:** Design proposal — awaiting review and approval before implementation
**Author:** Claude Code session, June 2026
**Affects:** `include/ssm_jacquard.h`, `src/ssm_jacquard.c`, `src/inference_engine.c`, `include/vm.h`
---
## 1. Executive Summary
The current L8 Jacquard selector is a **static 4-bit threshold classifier**: three metric
thresholds produce a 4-bit pattern that maps to one of 16 fixed modes controlling L2/L3/L5/L6.
It has no memory, no learning, and no ability to discover that the DoE-derived thresholds may
not be optimal for a specific deployed workload.
This document proposes replacing it with a **heat-ranked 8×128 adaptive config table** —
the same structure used by the hotword cache applied to configuration selection. The 128
entries cover all 2^7 DoE configurations (L1–L7). Benefit scores are updated via negative
feedback from the coupled (window, slope) convergence signal that the inference engine already
computes. DoE priors seed the initial scores so there is no cold-start penalty.
**Estimated additional overhead:** ~3–4 ns per heartbeat tick (0.03–0.04% of the 10 µs tick
period). Memory: ~10 KB, fully L1-cached.
**Verdict:** Worthwhile, conditional on the reward signal design described in Section 5.
See Section 9 for the full benefit/cost analysis.
---
## 2. What Exists Today
### 2.1 The Seven Loops
| Loop | Name | Current State | Reason |
|------|------|--------------|--------|
| L1 | Execution Heat Tracking | Permanently OFF | Harmful in 86% of top DoE configs |
| L2 | Rolling Window History | L8-controlled | ON if entropy > 0.75 |
| L3 | Linear Decay | L8-controlled | ON if temporal_decay > 0.5 |
| L4 | Pipelining Metrics | Permanently OFF | Harmful in 100% of top DoE configs |
| L5 | Window Width Inference | L8-controlled | ON if CV > 0.15 |
| L6 | Decay Slope Inference | L8-controlled | ON if CV > 0.15 AND temporal_decay > 0.3 |
| L7 | Adaptive Heartrate | Permanently ON | Beneficial in 71% of top DoE configs |
| L8 | Jacquard Policy Engine | — | Drives L2/L3/L5/L6 |
### 2.2 Current L8 Logic (`ssm_l8_update`, 156 lines total)
```c
int L2_bit = (entropy >= SSM_ENTROPY_HIGH_THRESHOLD); // 0.75
int L3_bit = (temporal >= SSM_TEMPORAL_DECAY_THRESHOLD); // 0.5
int L5_bit = (cv >= SSM_CV_HIGH_THRESHOLD); // 0.15
int L6_bit = (cv >= 0.15 && temporal >= SSM_TEMPORAL_DECAY_LOW_THRESHOLD);
target_mode = (L2<<3) | (L3<<2) | (L5<<1) | L6;
// hysteresis: commit only after SSM_HYSTERESIS_TICKS=5 consecutive votes
```
Four thresholds hardcoded from the DoE. No state beyond the hysteresis counter and current mode.
### 2.3 Top-5% DoE Validated Modes
| Mode | Bits (L2 L3 L5 L6) | Description |
|------|-------------------|-------------|
| C4 | 0 1 0 0 | Temporal locality only |
| C7 | 0 1 1 1 | Decay + both inferences |
| C9 | 1 0 0 1 | Rolling window + decay-slope inference |
| C11 | 1 0 1 1 | Rolling window + both inferences |
| C12 | 1 1 0 0 | Rolling window + decay, no inference |
These modes will seed the table with high initial benefit scores (see Section 6).
---
## 3. Signal Chain and Coupled Feedback Topology
Understanding the signal chain is prerequisite to understanding the reward signal.
The three variables — decay slope, window width, and heat trajectory — form a coupled
feedback system, not three independent loops.
```
┌─────────────────────────────────────────────────────────────────┐
│ COUPLED FEEDBACK SYSTEM │
│ │
│ decay_slope_q48 │
│ │ │
│ ▼ (applied each heartbeat tick) │
│ DictEntry.execution_heat ◄── decays at slope rate │
│ │ │
│ ▼ (word IDs recorded in execution order) │
│ RollingWindowOfTruth ◄── width = effective_window_size │
│ │ │
│ ▼ Phase 2B: linearize → heat trajectory[] │
│ Inference Engine │
│ │ │
│ ├─► Phase 2A: variance stable? ──► early-exit (most ticks) │
│ │ │
│ ├─► Phase 2C (L5): Levene's test │
│ │ └──► new effective_window_size ──────────────┐ │
│ │ │ │
│ └─► Phase 2D (L6): log regression │ │
│ └──► new decay_slope_q48 ────────────────────┼──┐ │
│ │ │ │
│ ┌────────────────────────────────────────────────────────┘ │ │
│ │ window feeds back into how much trajectory │ │
│ │ Phase 2B extracts next time │ │
│ │ │ │
│ └──► effective_window_size │ │
│ │ │
│ slope feeds back into heat distribution │ │
│ rolling window captures │ │
│ └──── decay_slope_q48 ◄──────────────────────────────────────┘ │
│ │
│ Parallel: adaptive pass adjusts window based on diversity │
│ (growth_rate_q48 vs threshold → shrink or grow) │
└─────────────────────────────────────────────────────────────────┘
```
### 3.1 Why the Coupling Matters
- A steeper decay slope → words cool faster → rolling window sees a more homogeneous
heat distribution → trajectory is less noisy → Levene's inflection shifts left
(narrower window sufficient) → Phase 2C infers a narrower window.
- A narrower window → Phase 2B extracts a shorter trajectory → log regression in
Phase 2D has fewer samples → slope estimate is noisier → less stable decay_slope_q48.
- They iterate toward a **joint attractor**: the window is exactly wide enough to give
the slope estimator reliable data, and the slope is exactly steep enough that the window
captures a stationary heat distribution. Neither is pushing the other any more.
This coupling is what makes the reward signal design non-trivial. See Section 5.
---
## 4. Proposed Architecture
### 4.1 Core Concept
Replace the 4-bit threshold classifier with a stratified heat-ranked table:
```
regime_scores[8][128] — benefit score per (regime, config)
regime_trials[8][128] — trial count per (regime, config)
```
The **8 regime bins** are the same 3-bit metric partition the current L8 already computes:
```c
regime = (entropy_high << 2) | (cv_high << 1) | temporal_high
```
Eight bins, one per combination of the three binary threshold tests. This is the same
partition as current L8 — we are not introducing new thresholds.
The **128 config entries** cover the full 2^7 DoE space: all combinations of L1–L7 as
independent binary variables. L1 and L4 are included with very low initial scores —
not disabled by fiat, but deprioritized by evidence.
### 4.2 Data Structures
```c
/* One entry in the config table */
typedef struct {
uint8_t config_bits; /* 7-bit mask: b6=L1, b5=L2, b4=L3, b3=L4,
b2=L5, b1=L6, b0=L7 */
uint32_t benefit_score; /* Heat analog — rises when stable, falls when not */
uint32_t trial_count; /* Total completed trials under this config */
uint64_t last_active_ns; /* Monotonic timestamp — supports score decay */
uint32_t prev_window; /* effective_window_size at end of last trial */
uint64_t prev_slope; /* decay_slope_q48 at end of last trial */
} SsmConfigEntry; /* ~32 bytes per entry */
/* Full stratified table */
typedef struct {
SsmConfigEntry entries[128]; /* Config metadata, static */
uint32_t regime_scores[8][128]; /* Benefit score per regime per config */
uint32_t regime_trials[8][128]; /* Trial count per regime per config */
uint8_t current_config; /* Index of active config (0–127) */
uint8_t current_regime; /* Regime bin at start of current trial */
uint32_t trial_tick_count; /* Ticks elapsed in current trial (relative time) */
uint32_t anova_exits_this_trial; /* Phase 2A early-exits accumulated */
uint32_t total_regime_trials[8]; /* Total trials per regime (for UCB ln term) */
} SsmConfigTable;
```
**Memory footprint:**
| Structure | Size |
|---|---|
| `SsmConfigEntry[128]` | 128 × 32 = 4,096 bytes |
| `regime_scores[8][128]` (uint32_t) | 4,096 bytes |
| `regime_trials[8][128]` (uint32_t) | 4,096 bytes |
| Trial state + totals | ~64 bytes |
| **Total** | **~12 KB** |
The entire table fits in L1 cache (32–64 KB typical). The access pattern is regime-local:
each trial-end touches 128 contiguous uint32_t values for the current regime, then stops.
This is the same cache-friendly pattern the hotword cache exploits.
### 4.3 `ssm_l8_state_t` Changes
Current `ssm_l8_state_t` has: `current_mode`, `hysteresis_counter`, `pending_mode`.
New `ssm_l8_state_t` adds a pointer to the config table (keeping the existing fields
for backward compatibility during transition, removing them once validated):
```c
typedef struct {
ssm_l8_mode_t current_mode; /* Keep for compatibility */
uint32_t hysteresis_counter; /* Keep for compatibility */
ssm_l8_mode_t pending_mode; /* Keep for compatibility */
SsmConfigTable *table; /* NULL = use legacy threshold mode */
} ssm_l8_state_t;
```
Gated behind `table != NULL` so the old path remains functional during development
and testing.
---
## 5. Reward Signal Design
### 5.1 The Circularity Problem
A naive reward signal based on correction magnitude within a trial will produce wrong results:
- A config with **L5+L6 enabled** always shows non-zero `Δwindow` and `Δslope` — because
the inference engine is running and making corrections. Large corrections on the first trial
after a config switch may reflect genuine convergence toward the attractor, not config failure.
- A config with **L5+L6 disabled** always shows zero `Δwindow` and `Δslope` — because
nothing is running to update them. This would be incorrectly rewarded as "stable."
**The fix:** measure **joint convergence across consecutive trials**, not correction magnitude
within a trial. The system is converging when the corrections are getting *smaller* from one
trial to the next — not when they are zero in one trial.
### 5.2 Joint Error Signal
At the end of each trial, record the current (window, slope) pair as `(prev_window, prev_slope)`
in the `SsmConfigEntry`. At the end of the *next* trial under the same config and same regime,
compute joint error:
```
Δw = |window_trial_N+1 - window_trial_N| / window_trial_N
Δs = |slope_trial_N+1 - slope_trial_N| / slope_trial_N
joint_error = sqrt(Δw² + Δs²) / sqrt(2) ← normalized RMS in [0, 1]
stability = 1.0 - clamp(joint_error, 0.0, 1.0)
```
When the window and slope have stopped moving relative to each other, `joint_error → 0`
and `stability → 1.0`. When they are still adjusting, `joint_error > 0` and
`stability < 1.0`.
### 5.3 ANOVA Rate as Secondary Signal
The ANOVA early-exit rate (Phase 2A) is a fast, cheap proxy for system stability:
it fires when variance hasn't changed enough to justify full inference. It is a necessary
but not sufficient condition for joint attractor convergence:
```
anova_stability = anova_exits_this_trial / trial_ticks ∈ [0, 1]
```
### 5.4 Combined Stability Score
```
combined_stability = w_joint × stability + w_anova × anova_stability
```
#### What each signal actually measures
`stability` (from `joint_error`) is **output convergence**: did the inference engine's
corrections reach a fixed point? It requires at least one of L5 or L6 to be active —
if neither is running, `Δwindow = Δslope = 0` always and the signal collapses (Section 5.7).
`anova_stability` is **input stationarity**: is the execution trajectory quiet enough that
the inference engine decided not to bother computing? It fires regardless of L5/L6 state,
making it the only signal available when both are disabled.
These are not redundant. A config can show:
- High `anova_stability`, low `stability`: input is quiet (ANOVA skips often) but corrections
keep churning — window and slope are wandering on a flat input landscape. Indicates
misconfigured L5/L6 or an under-constrained attractor.
- Low `anova_stability`, high `stability`: input is noisy/dynamic but corrections have
settled — the config found a stable operating point despite volatile input. Desirable.
- Both high: ideal. Both low: still converging, not yet settled.
#### The partial-inference bias problem
When only one of L5 or L6 is active, `joint_error` is only half-informed:
- L5-on, L6-off: `Δslope = 0` always (slope frozen) → `joint_error = |Δw|/sqrt(2)`,
biased toward 1.0 because the frozen component contributes zero.
- L5-off, L6-on: symmetric bias in the other dimension.
A fixed weight like 0.75 treats both the one-active and both-active cases identically,
overstating the signal quality in the one-active case.
#### Option A: Fixed weights (simpler, conservative)
```
w_joint = 0.75, w_anova = 0.25
```
This is the design-conservative choice. The joint signal dominates when it is available;
ANOVA fills in when it cannot run (full fallback in Section 5.7). The 3:1 ratio reflects
the ordering: output convergence is what we ultimately care about; input stationarity
is a proxy that is easier to measure. These weights need DoE validation — they are an
informed initial guess, not a derived result.
#### Option B: Activity-weighted (principled, slightly more complex)
```c
active_outputs = L5_enabled + L6_enabled; /* ∈ {0, 1, 2} */
w_joint = active_outputs / 2; /* 0.0, 0.5, or 1.0 */
w_anova = 1 - w_joint;
```
When both L5 and L6 are active: 100% joint (both components of `joint_error` valid).
When one is active: 50/50 (half the joint signal is meaningful, ANOVA fills the rest).
When neither is active: 100% ANOVA (collapses cleanly to the Section 5.7 special case,
eliminating the need for a separate branch).
This is strictly more correct than Option A for the partial-inference cases. It costs
three integer operations per trial end. The tradeoff: it makes the effective weight
a function of the current config, which complicates reasoning about the reward surface
when comparing configs with different L5/L6 settings.
#### Decision required
This is the most consequential tunable in the design. **Recommend starting with Option A
at the conservative 0.75/0.25 split and adding instrumented logging of both components**
so the effective ratio can be observed over real workload runs before any adjustment.
Option B should be considered if the logs show systematic bias in the partial-inference
configs (e.g., L5-on/L6-off configs consistently overscoring their L5-on/L6-on neighbors).
### 5.5 Negative Feedback Reward Update
```
benefit_delta = GAIN × (combined_stability - 0.5) × 2.0
new_score = old_score × DECAY_FACTOR + benefit_delta
new_score = clamp(new_score, SCORE_MIN, SCORE_MAX)
```
- `combined_stability > 0.5`: the config is driving the system toward its attractor → positive
delta → benefit_score rises.
- `combined_stability < 0.5`: the config is preventing convergence → negative delta →
benefit_score falls.
- `combined_stability = 0.5`: neutral → no update.
`DECAY_FACTOR < 1.0` ensures scores decay over time, preventing a config from holding
the top position indefinitely based on past performance.
#### DECAY_FACTOR stability analysis
The half-life of a benefit score is expressed in **trials** — the natural relative-time unit
for this system. Wall-clock conversion is L7's concern and is not the right framing here.
```
half_life_trials = ln(0.5) / ln(DECAY_FACTOR)
```
At `DECAY_FACTOR = 0.99`:
```
half_life_trials ≈ 69 trials
```
The key assumption: **regime dwell time, measured in trials**. A config earns its score
over many trials in a given regime. If the system typically dwells in a regime for ~200 trials
before the metric signature shifts, a 69-trial half-life means scores from the previous dwell
carry moderate weight into the next — which is appropriate. If regimes shift every ~20 trials,
the half-life is too long and the table remembers the wrong regime. If regimes dwell for
~2,000 trials, the half-life is fine and the exploration bonus from UCB prevents stagnation.
**Effective-zero crossover:**
```
0.99^n = 0.01 → n ≈ 458 trials
```
This is the memory horizon — how many trials back the table looks. Flag `SSM_SCORE_DECAY_FACTOR`
as a first-class tunable. Calibrate it against observed regime dwell time in trials, not
against wall-clock expectations.
### 5.6 Minimum Trial Duration
Each trial must contain at least two inference cycles — one to establish `(prev_window,
prev_slope)` and one to measure how much they moved. Since inference runs every
`HEARTBEAT_INFERENCE_FREQUENCY = 1,000` ticks:
```
SSM_MIN_TRIAL_TICKS = 2 × HEARTBEAT_INFERENCE_FREQUENCY = 2,000 ticks
```
**This is a relative-time measure, not a wall-clock measure — intentionally so.**
The tick is the fundamental unit of relative time in this system. L7 (adaptive heartrate)
varies the *wall-clock duration* of a tick based on observed workload. That variation is the
system's own response to workload; it is not noise. A trial lasting 2,000 ticks at a slower
L7 heartrate is a trial over a longer relative-time span, which corresponds to more actual
system activity — it is a larger, not smaller, sample. Anchoring to wall clock here would
import an absolute time dependency into a system built deliberately around relative time with
CV promises.
The correct framing: `SSM_MIN_TRIAL_TICKS` is a promise — "this trial will not end until
the system has processed at least 2,000 of its own ticks." What that means in nanoseconds
is L7's concern, not the adaptive selector's.
`SSM_MIN_TRIAL_TICKS` is a first-class tunable in `starforth_config.h`. Larger values give
more stable reward estimates; smaller values allow faster adaptation to regime changes.
### 5.7 Special Case: L5 and L6 Both Disabled
When both L5 and L6 are disabled in the current config, `Δwindow` and `Δslope` from the
inference engine are always zero (the values are frozen). The joint error signal is
meaningless — both numerator and denominator are zero.
In this case, fall back to ANOVA rate alone:
```c
if (!L5_enabled && !L6_enabled) {
combined_stability = anova_stability;
}
```
This correctly evaluates "stability without active inference" — does the system at least
not need correcting when left alone?
---
## 6. DoE Prior Seeding
The 38,400-run DoE provides empirical priors. These seed `regime_scores` at initialization
so the selector immediately gravitates toward known-good configs on first deployment.
### 6.1 Seeding Strategy
| Config category | Initial score | Reasoning |
|---|---|---|
| Top-5% configs (C4, C7, C9, C11, C12) in their 4-bit form | `SCORE_MAX × 0.80` | DoE-validated |
| Other configs with L1=0, L4=0 | `SCORE_MAX × 0.40` | Not validated but not harmful |
| Configs with L1=1 OR L4=1 | `SCORE_MAX × 0.10` | DoE found harmful on average |
| Configs with L1=1 AND L4=1 | `SCORE_MAX × 0.05` | Doubly harmful |
Note: the top-5% configs from the DoE are defined in 4-bit (L2/L3/L5/L6) space. For the
7-bit table, each top-5% 4-bit config maps to multiple 7-bit entries (L1=0, L4=0, L7=1 held
fixed). All such mappings receive the same high initial score.
### 6.2 Regime-Specific Seeding
The DoE ran across all regimes combined. If regime-specific DoE data is available in the
future, `regime_scores[r]` can be seeded independently per regime. For now, all 8 regimes
receive the same initial scores — the table will differentiate them over time through runtime
learning.
---
## 7. Config Selection: UCB Algorithm
### 7.1 Selection Score
At each trial end, the selector computes a UCB (Upper Confidence Bound) score for all 128
configs in the current regime and picks the highest:
```
ucb_score[r][c] = regime_scores[r][c]
+ UCB_K × sqrt(ln(total_regime_trials[r]) / regime_trials[r][c])
```
- `regime_scores[r][c]`: exploitation term — how well config C has performed in regime R
- `UCB_K × sqrt(...)`: exploration term — bonus for under-tried configs; shrinks as
`regime_trials[r][c]` grows toward `total_regime_trials[r]`
**UCB_K** controls exploration aggressiveness. Suggest starting at `UCB_K = 0.1 × SCORE_MAX`.
Too large: wastes trials on known-bad configs. Too small: never explores.
### 7.2 Implementation Cost
- `ln(total_regime_trials[r])`: computed **once** per trial end, reused for all 128 entries
- Per config: 1 divide + 1 integer sqrt (~10 cycles) + 1 multiply + 1 add
- 128 configs × ~4 ops × 2 ns = **~1–2 µs per trial end**
- Amortized over 2,000-tick trial: **~0.5–1 ns per tick**
Integer sqrt via Newton's method (4–5 iterations, convergence guaranteed for uint32_t):
no floating point, no libm dependency. Compatible with freestanding kernel builds.
### 7.3 Bootstrap: First Two Trials
The first trial under any (regime, config) pair has `regime_trials[r][c] = 0`,
making the UCB exploration term undefined (`ln/0`). Bootstrap rule:
```
if regime_trials[r][c] == 0:
ucb_score[r][c] = SCORE_MAX // treat unexplored as maximally interesting
```
This ensures every config gets at least one trial in each regime before the UCB formula
takes over — equivalent to the "try everything once" initialization of UCB1.
---
## 8. Re-Ranking and Selection Path
Mirrors `dict_adaptive_optimization_pass` / `dict_reorganize_buckets_by_heat` exactly.
### 8.1 Per-Trial-End Sequence
```
1. Compute regime bin for current metrics (same 3 comparisons as today)
2. Compute joint_error and anova_stability for completed trial
3. Update regime_scores[current_regime][current_config] with benefit_delta
4. Increment regime_trials[current_regime][current_config]
5. Apply score decay: regime_scores[r][c] *= DECAY_FACTOR
6. Compute ucb_score for all 128 configs in current_regime
7. Select next_config = argmax(ucb_score)
8. Apply next_config's 7-bit L1–L7 mask to ssm_config
9. Reset trial state: trial_tick_count=0, anova_exits_this_trial=0
10. Record current (window, slope) into entries[current_config].prev_*
```
### 8.2 Per-Tick Sequence (replaces current ssm_l8_update)
```
1. Increment trial_tick_count
2. If inference ran this tick: increment anova_exits_this_trial (if early-exited)
3. If trial_tick_count >= SSM_MIN_TRIAL_TICKS:
run per-trial-end sequence (steps 1–10 above)
```
Trial duration is a relative-time promise in ticks. L7 adaptive heartrate affects wall-clock
duration but not relative-time semantics. See Section 5.6 for full rationale.
---
## 9. Overhead Estimates
All figures assume 3 GHz CPU, base heartrate tick, 1,000-tick inference frequency,
2,000-tick minimum trial period. Trial duration in wall-clock time varies with L7.
### 9.1 Per-Tick Additions
| Operation | Cost | Notes |
|---|---|---|
| Regime bin computation | 0 ns | Identical to today's 3 comparisons |
| `trial_tick_count++` | ~0.3 ns | 1 integer add |
| ANOVA exit accumulation | ~0.3 ns | 1 conditional add |
| Trial-end check | ~0.3 ns | 1 compare |
| **Per-tick total** | **~1 ns** | 0.01% of 10 µs tick |
### 9.2 Per-Trial-End Additions (every 2,000 ticks = 20 ms)
| Operation | Cost | Amortized/tick |
|---|---|---|
| Joint error computation (Δw, Δs, RMS) | ~50 ns | 0.025 ns |
| ANOVA rate computation | ~10 ns | 0.005 ns |
| Combined stability + benefit_delta | ~30 ns | 0.015 ns |
| Score update + decay (1 array write) | ~15 ns | 0.0075 ns |
| UCB over 128 entries (int sqrt ×128) | ~1,500 ns | 0.75 ns |
| Argmax selection | ~50 ns | 0.025 ns |
| **Per-trial-end total** | **~1,655 ns** | **~0.83 ns/tick at base heartrate** |
### 9.3 Memory Access
| | Size | Cache behavior |
|---|---|---|
| `regime_scores[r][*]` (128 uint32_t) | 512 bytes per regime | ~8 cache lines, stays hot |
| `SsmConfigEntry[128]` | 4,096 bytes | Fits in L1; accessed sequentially |
| Full table | ~12 KB | Fits in L1 (32–64 KB typical) |
### 9.4 Summary Against Existing Costs
| Component | ns/tick amortized | % of 10 µs tick |
|---|---|---|
| Existing `ssm_l8_update` (today) | ~4 ns | 0.04% |
| Existing inference engine (20–50 µs every 1,000 ticks) | 20–50 ns | 0.20–0.50% |
| **New per-tick accumulation** | **~1 ns** | **0.01%** |
| **New per-trial-end (amortized)** | **~0.83 ns** | **0.008%** |
| **New L8 total addition** | **~2 ns** | **0.02%** |
| **New L8 total (replacing old L8)** | **~2 ns** | **0.02%** |
The new L8 is marginally *cheaper* than the current one in per-tick terms
(today's `ssm_l8_update` runs every tick; the new one accumulates cheaply per tick and
runs the heavy logic only at trial end, amortized over 2,000 ticks).
The existing Levene's test `malloc` inside Phase 2C costs more per call than the entire
new L8 reward + UCB selection combined.
---
## 10. Files Changed
| File | Change | Lines added (est.) |
|---|---|---|
| `include/ssm_jacquard.h` | Add `SsmConfigEntry`, `SsmConfigTable`; extend `ssm_l8_state_t` | +80 |
| `src/ssm_jacquard.c` | Add `ssm_l8_init_table()`, `ssm_l8_seed_from_doe()`, `ssm_l8_update_trial()`, `ssm_l8_select_next_config()`, `ssm_rerank_ucb()`; keep old `ssm_l8_update` as legacy path | +250 |
| `src/inference_engine.c` | Add `window_delta_q48` and `slope_delta_q48` to `InferenceOutputs`; populate them (already computed internally, just not exposed) | +10 |
| `include/vm.h` (via `inference_engine.h`) | Two new fields on `InferenceOutputs` | +5 |
| `src/ssm_jacquard.c` | DoE prior seeding table (static const array of 128 seed scores) | +40 |
**No changes to:** `rolling_window_of_truth.c`, `heartbeat_export.c`, `physics_runtime.c`,
`framebuffer.c`, `vt100.c`, any kernel or boot code.
Total estimated addition: **~385 lines**, isolated entirely within the L8 and inference
engine files.
---
## 11. What Doesn't Change
- The five inference engine phases (2A–2D) are architecturally unchanged. Two small
outputs are added (`window_delta_q48`, `slope_delta_q48`) — values already computed
internally that are simply not currently surfaced.
- The `ssm_config_t` L1–L7 bit flags are unchanged. Everything downstream reads them
the same way.
- The rolling window, heartbeat, heat tracking, hotword cache, dictionary optimization
— all unchanged.
- The kernel build (freestanding) is unaffected. `SsmConfigTable` uses only `stdint.h`
types, no libc allocation (table is statically sized), no floating point
(all arithmetic in Q48.16 or integer).
---
## 12. Open Questions for Captain Bob
These require decisions before coding begins:
**Q1: Include L1 and L4 in the 7-bit space?**
Current plan: yes, seeded at 5–10% of `SCORE_MAX`. The DoE found them harmful on average
but the table can discover exceptions if they exist. Risk: UCB exploration may occasionally
select them and hurt performance during that trial. Mitigation: low seed + score decay means
they stay near the bottom. Alternative: exclude them (use 5-bit space, 32 configs) and keep
L7 hardwired to ON (as today).
**Q2: Tunable constants — bake in or expose via `starforth_config.h`?**
Proposed new constants:
- `SSM_MIN_TRIAL_TICKS` (suggest 2000 — relative-time floor, guarantees ≥ 2 inference
cycles; wall-clock meaning is L7's concern, not ours; see Section 5.6)
- `SSM_SCORE_MAX` (suggest 65535 to match Q48.16 range intuition)
- `SSM_SCORE_DECAY_FACTOR` (suggest 0.99 ≈ Q48.16 64881; half-life ~69 trials —
calibrate against observed regime dwell time in trials; see Section 5.5)
- `SSM_UCB_K` (suggest `SSM_SCORE_MAX / 10`)
- `SSM_REWARD_GAIN` (suggest `SSM_SCORE_MAX / 4`)
- `SSM_JOINT_WEIGHT` (suggest 0.75 for Option A; see Section 5.4)
- `SSM_ANOVA_WEIGHT` (suggest 0.25 for Option A; or use activity-weighted Option B)
All of these map naturally alongside the existing `ROLLING_WINDOW_SIZE`, `SSM_HYSTERESIS_TICKS`
etc. in `include/starforth_config.h`.
**Q3: Regime-specific seeding now or later?**
Current plan: all 8 regimes seeded identically from DoE priors. Regime-specific DoE
analysis (if the raw run data is available broken out by metric regime) would improve
cold-start behavior. Can be deferred — the table learns regime-specific scores at runtime.
**Q4: Legacy threshold mode as compile-time flag or runtime gate?**
Suggested: runtime gate (`table != NULL` in `ssm_l8_state_t`). Allows A/B testing the
old vs new L8 without recompilation. Alternative: compile-time `#ifdef SSM_L8_ADAPTIVE`
to keep the kernel build lean.
**Q5: How to validate correctness before running the full DoE?**
Suggested validation sequence (hosted VM only):
1. `make -f Makefile.starkernel fbtest` — confirms no regression in framebuffer
2. `make test` — confirms all 936+ FORTH words still pass
3. Run `--doe` mode for 300 reps each with and without `SSM_L8_ADAPTIVE` defined;
compare variance and mean execution time. The adaptive selector should produce equal
or better variance at equal or lower mean time after a warm-up period of ~1000 trials.
---
## 13. Benefit / Cost Assessment
### 13.1 Concrete Expected Benefits
**1. Regime-specific optimality.**
The DoE established which configs win *on average*. This table learns which config wins
*for this specific workload in this specific regime*. For FORTH programs with unusual heat
distributions (e.g., heavily recursive, deeply compositional, or with very stable vs very
bursty execution patterns), the optimal config per regime may differ from the DoE average.
The table discovers this without re-running the DoE.
**2. No manual tuning for new workloads.**
Current L8: if the DoE thresholds are miscalibrated for a specific FORTH application, the
classifier is stuck in the wrong mode permanently. The adaptive table self-corrects within
a few hundred trial periods (a few seconds of runtime at 20ms trials).
**3. Joint convergence signal closes the feedback loop.**
The inference engine currently produces (window, slope) outputs with no feedback about
whether those outputs are causing the system to converge. The reward signal adds this
feedback, making the overall system a genuine closed-loop controller rather than
open-loop inference.
**4. Architecturally consistent.**
The hotword cache, bucket reorganization, and adaptive window already use heat-ranked
structures. L8 becoming heat-ranked completes the pattern — everything in the SSM is
adaptive and data-driven, not hardcoded.
### 13.2 Honest Caveats
**1. The 0% algorithmic variance result.**
The DoE showed the system converges to the same attractor states from any of the 128
starting configurations. If the attractor is already well-characterized and robust, the
adaptive table is solving a problem that doesn't significantly affect final-state behavior
for most workloads. The benefit is primarily in *how quickly* the system reaches its
attractor, not *where* the attractor is.
**2. L1 and L4 almost certainly stay at the bottom.**
The DoE evidence against them is overwhelming (86% and 100% harmful respectively). The
exploration of L1 and L4 slots in the 128-entry space is methodologically correct but
practically likely to produce no new findings. The useful learning is within the 16-config
subspace of {L2, L3, L5, L6} combinations, stratified by regime — which the current L8
already approximates with fixed thresholds.
**3. Reward signal parameter sensitivity.**
The weights (`w_joint`, `w_anova`), gain, decay factor, UCB_K, and trial duration interact.
The DoE that validated the current system was designed to avoid exactly this kind of
parameter sensitivity. A second DoE over the adaptive L8's parameters may be needed to
find the optimal operating point — or the parameters can be set conservatively and left
fixed, accepting slightly suboptimal but stable behavior.
**4. Complexity increase is real.**
`ssm_jacquard.c` is currently 156 clean lines. The addition is ~385 lines of nontrivial
logic in the heartbeat thread. Bugs here affect the adaptation rate, not correctness
(worst case: the selector sticks on a suboptimal config, not on an incorrect config),
but the surface area grows meaningfully.
### 13.3 Verdict
**Worth doing.** The overhead is negligible by any measure. The architectural coherence
with the existing hotword cache is a genuine advantage. The joint convergence reward signal
is a principled design that closes a feedback loop the current system leaves open.
The primary condition: **get the reward signal right before writing any other code**.
Specifically, resolve Q1 (L1/L4 inclusion), confirm the `window_delta` / `slope_delta`
output additions to `InferenceOutputs`, and agree on the `min_trial_ticks` value.
Everything else is mechanical once those are fixed.
The primary risk is not overhead or correctness — it is parameter sensitivity in the
reward signal. Suggest conservative initial values (low GAIN, high DECAY, moderate UCB_K)
and validate against the existing DoE baseline before changing any parameters.
---
*End of design document. Awaiting review and approval.*
+571
View File
@@ -0,0 +1,571 @@
# M7.1: Init Capsule Architecture
**Status:** Shipped and live, not just designed — this document predates the actual
capsule/Tripod/Stadium work; see `FABRIC-0.md`/`FABRIC-1.md`/`FABRIC-2.md` for real status
(per `.claude/CLAUDE.md`'s standing redirect)
**Branch:** `master` (post-split; the old `lithosananke` branch no longer exists in this repo)
**Prerequisite:** M7 (VM Parity Validation)
## The Immutable Law
> **Exactly ONE production INIT `(p)` defines a baby VM.**
>
> No INIT is shared implicitly.
> No base INIT exists.
> Any similarity between VMs is the result of deliberate reuse by Mama.
This rule is non-negotiable. Everything in this document exists to enforce it.
---
## 1. DOMAIN vs PERSONALITY
### 1.1 DOMAIN (Mama-only Concept)
**DOMAIN** is a *construction and selection space* that exists **only in Mama FORTH**.
DOMAIN is used to:
- Classify *kinds* of VMs (e.g., HTTP, storage, control-plane)
- Compose and curate block sets
- Run experiments `(e)`
- Decide which block set is promoted to production `(p)`
DOMAIN:
- Is **never** executed by babies
- Is **never** visible to babies
- Is **not** part of VM truth
- Leaves **no** runtime trace in the baby
DOMAIN answers: *"What kind of thing is Mama trying to create?"*
### 1.2 PERSONALITY (Baby-only Reality)
**PERSONALITY** is the *result* of Mama's work.
A baby VM receives:
- **Exactly one `(p)` INIT**
- Executed exactly once at birth
- Which defines its entire worldview
PERSONALITY:
- Defines role, capabilities, invariants, vocabulary
- Is immutable for the lifetime of the VM
- Is the only truth the VM knows
PERSONALITY answers: *"What am I?"*
**The baby has no knowledge of DOMAIN.**
---
## 2. What is an Init Capsule?
An INIT capsule is **not**:
- A shared base
- A layered inheritance mechanism
- A runtime-selectable script
- A file or "boot code"
An INIT capsule **is**:
- An **ordered, immutable block sequence**
- A **truth-bearing unit**
- Executed **exactly once** at VM birth
- The **sole source of identity** for that VM
- **Referentially addressable** — by content hash, not name
- **Observationally enumerable** — Mama can list all capsules
Think: ROM image + provenance, not "config."
---
## 3. Architecture Decision
### Option 1: One Big Memory Blob (Indexed)
Single contiguous arena with descriptor table + payload region.
### Option 2: Individual Named Blobs
Separate addressable entities with symbolic names.
### Decision: Hybrid
**Physically:** Option 1 — single arena, indexed by ID + offset
**Logically:** Option 2 — capsules declared symbolically at build time
At runtime: **symbols do not exist.** Only facts (hashes, offsets, lengths).
> Names lie. Hashes don't.
---
## 4. Data Structures
### 3.1 CapsuleDesc (64 bytes, cache-line aligned)
```c
typedef struct {
uint64_t magic; /* 0x00: 'CAPS' | ver | hashAlg | reserved */
uint64_t capsule_id; /* 0x08: == content_hash (content-addressed) */
uint64_t content_hash; /* 0x10: hash of payload bytes */
uint64_t offset; /* 0x18: byte offset into payload arena */
uint64_t length; /* 0x20: payload length in bytes */
uint32_t flags; /* 0x28: state + policy bits */
uint32_t owner_vm; /* 0x2C: 0 = mama, else child VM ID */
uint64_t birth_count; /* 0x30: how many VMs born from this */
uint64_t created_ns; /* 0x38: monotonic timestamp at registration */
} CapsuleDesc; /* 0x40 = 64 bytes */
```
### 3.2 Magic Field Packing
```
bits 0..31 : 'CAPS' (0x53504143 little-endian)
bits 32..39 : version (0 for v0)
bits 40..47 : hashAlg (enum CapsuleHashAlg)
bits 48..63 : reserved (zero)
```
### 3.3 CapsuleDirHeader
```c
typedef struct {
uint64_t magic; /* 'CAPD' | ver | reserved */
uint64_t arena_base; /* phys or virt base of payload arena */
uint64_t arena_size; /* bytes */
uint32_t desc_count; /* current number of descriptors */
uint32_t desc_capacity; /* max (fixed at compile time for Phase A) */
uint64_t dir_hash; /* hash of descriptor table (for parity) */
} CapsuleDirHeader;
```
### 3.4 CapsuleRunRecord (for DoE logging)
```c
typedef struct {
uint64_t run_id; /* sequential run identifier */
uint32_t vm_id; /* which VM executed this */
uint32_t reserved;
uint64_t capsule_id; /* which capsule was run */
uint64_t capsule_hash; /* hash at time of execution */
uint64_t pre_dict_hash; /* dictionary state before run */
uint64_t post_dict_hash; /* dictionary state after run */
uint64_t started_ns; /* monotonic start time */
uint64_t ended_ns; /* monotonic end time */
uint32_t result_code; /* execution result */
uint32_t flags; /* run flags (mode, etc.) */
} CapsuleRunRecord;
```
---
## 5. Content Addressing
**Rule:** `capsule_id` MUST be derived from `content_hash`.
For v0: `capsule_id == content_hash` (identity)
This ensures:
- No sequential ID collisions
- No "what's the salt" questions
- Facts only, no oracles
---
## 6. Hash Algorithm
**v0 Choice:** xxHash64
- Deterministic across hosted + kernel
- Easy to implement freestanding
- Fast
- Good enough collision resistance for practice
**Future-proofing:** The `hashAlg` field in magic allows upgrade to SHA-256 or BLAKE3 without breaking history.
```c
typedef enum {
CAPSULE_HASH_XXHASH64 = 0,
CAPSULE_HASH_SHA256 = 1,
CAPSULE_HASH_BLAKE3 = 2,
} CapsuleHashAlg;
```
---
## 7. Flags
### 6.1 State Flags
```c
#define CAPSULE_FLAG_ACTIVE 0x00000001 /* eligible for use */
#define CAPSULE_FLAG_REVOKED 0x00000002 /* birth-blocked forever */
#define CAPSULE_FLAG_DEPRECATED 0x00000004 /* eligible but discouraged */
#define CAPSULE_FLAG_PINNED 0x00000008 /* immune to GC */
```
### 6.2 Mode Flags (Production vs Experiment)
```c
#define CAPSULE_FLAG_PRODUCTION 0x00000010 /* (p) truth-bearing */
#define CAPSULE_FLAG_EXPERIMENT 0x00000020 /* (e) workload only */
```
**Invariant:** Exactly one of (p) or (e) must be set.
```c
#define CAPSULE_MODE_VALID(f) \
((((f) & CAPSULE_FLAG_PRODUCTION) != 0) ^ \
(((f) & CAPSULE_FLAG_EXPERIMENT) != 0))
```
If both or neither are set: **invalid capsule.**
### 6.3 Revocation Semantics
A revoked capsule:
- Remains enumerable (for audit)
- Remains hashable (for provenance)
- Is birth-blocked (mama refuses to bind it)
**REVOKED always overrides ACTIVE.**
---
## 8. Eligibility Rules
### 7.1 Birth Eligibility
A VM may be born **only** from a (p) capsule:
```c
#define BIRTH_ELIGIBLE(flags) \
(((flags) & CAPSULE_FLAG_PRODUCTION) && \
((flags) & CAPSULE_FLAG_ACTIVE) && \
!((flags) & CAPSULE_FLAG_REVOKED))
```
If mama attempts to bind an (e) capsule at birth: **hard refusal.**
No warnings. No fallback. No exceptions.
### 7.2 DoE/Runtime Eligibility
**(e) capsules exist ONLY in Mama's domain.** Baby VMs never see them, never execute them, never know they exist.
Mama may execute:
- **(e) capsules** freely on herself (subject to policy)
- **(p) capsules** only as birth capsules for babies (never as workloads)
This prevents the deadly mistake:
> "Let's just rerun a production init as an experiment."
No. That turns truth into a variable.
### 7.3 Twins, Triplets, and Variants
**Twins/Triplets/Variants are not special constructs.** They are simply:
- Multiple VMs born from the **same (p) capsule** (twins)
- VMs born from **similar (p) capsules** (variants)
Any similarity between VMs is the result of:
1. Mama deliberately choosing the same (p) capsule for birth
2. Mama creating similar (p) capsules from shared block sets
There is no implicit inheritance. There is no base class. If two VMs share behavior, it's because Mama *made* them share it — not because of any hidden mechanism.
---
## 9. Parity Integration
Birth becomes a parity checkpoint:
```
PARITY:BIRTH vm_id=42 capsule_id=0xA13F mode=p capsule_hash=0x... dict_hash=0x...
```
DoE runs are logged separately:
```
PARITY:RUN vm_id=42 run_id=17 capsule_id=0xBEEF mode=e pre_dict=0x... post_dict=0x...
```
### 8.1 Truth Chain
For any VM, you can prove:
1. Which capsule created it (birth_capsule_id)
2. What the capsule contained (content_hash)
3. What dictionary state resulted (dict_hash)
4. What workloads were run after birth (CapsuleRunRecord log)
This is **provenance**. This is **science**. This is **patent-grade**.
---
## 10. Arena Placement
### Phase A: Static (M7.1 - M8)
- Descriptor table + payload arena live in `.rodata` or dedicated section
- Built by tooling (or compiled-in)
- Zero allocator dependency
- Deterministic
```c
/* In kernel image */
extern const CapsuleDirHeader capsule_directory;
extern const CapsuleDesc capsule_descriptors[];
extern const uint8_t capsule_arena[];
```
### Phase B: Dynamic (Future)
- Mama manages RAM arena
- Runtime capsule registration
- Optional persistence via memory-mapped block store
**Key:** `CapsuleDesc` doesn't care where bytes come from. It only cares about `offset`/`length` within "the arena."
---
## 11. Capacity
### Phase A: Fixed Array
```c
#define MAX_CAPSULES 256
```
`CapsuleDirHeader` includes `desc_count` and `desc_capacity`.
Growable arrays can come later without interface changes.
---
## 12. Who Writes Descriptors?
### Phase A (Static)
- **Build tooling** generates descriptors
- Kernel boots with sealed capsule directory
- Mama reads and binds, never writes
### Phase B (Dynamic)
- **Mama** appends new capsules to RAM arena
- Only mama writes descriptors at runtime
- **Children never write descriptors, ever**
---
## 13. VM Birth Protocol
1. Mama selects **one production INIT `(p)`** (by content hash)
2. Mama validates eligibility:
- `CAPSULE_FLAG_PRODUCTION` set
- `CAPSULE_FLAG_ACTIVE` set
- `CAPSULE_FLAG_REVOKED` not set
3. Mama allocates a new VM
4. INIT blocks are **copied** into the VM execution window (RAM blocks 0–2047)
5. INIT blocks are **executed sequentially**
6. Execution window is **cleared**
7. VM begins life with its **PERSONALITY fully imprinted**
8. Mama logs parity checkpoint:
```
PARITY:BIRTH vm_id=N capsule_id=X mode=p capsule_hash=H dict_hash=D
```
9. Mama increments `capsule.birth_count`
**There is:**
- No shared INIT
- No implicit base
- No post-birth INIT execution
- No runtime selection
**The baby VM cannot:**
- Enumerate sibling capsules
- Mutate the arena
- Know its own capsule ID (optional: can query mama)
- See or know about DOMAIN
---
## 14. DoE Execution Protocol
1. Mama selects workload capsule(s)
2. Mama validates eligibility:
- `CAPSULE_FLAG_EXPERIMENT` set
- `CAPSULE_FLAG_ACTIVE` set
- `CAPSULE_FLAG_REVOKED` not set
3. For each workload:
a. Record `pre_dict_hash`
b. Execute capsule on target VM
c. Record `post_dict_hash`
d. Log run record:
```
PARITY:RUN vm_id=N run_id=R capsule_id=X mode=e pre_dict=P post_dict=Q
```
**DoE runs do not change the VM's birth truth.** They are experiments, not identity.
---
## 15. Validation Function
```c
typedef enum {
CAPSULE_VALID = 0,
CAPSULE_ERR_BAD_MAGIC,
CAPSULE_ERR_BAD_VERSION,
CAPSULE_ERR_BAD_HASH_ALG,
CAPSULE_ERR_BOUNDS,
CAPSULE_ERR_MODE_INVALID,
CAPSULE_ERR_REVOKED_ACTIVE,
CAPSULE_ERR_HASH_MISMATCH,
} CapsuleValidateResult;
CapsuleValidateResult capsule_validate(
const CapsuleDesc *desc,
const uint8_t *arena_base,
uint64_t arena_size,
int verify_hash /* if true, recompute and compare */
);
```
Checks:
1. Magic signature and version
2. Hash algorithm is known
3. `offset + length <= arena_size`
4. Exactly one of (p)/(e) is set
5. REVOKED and ACTIVE not both set (REVOKED wins, but warn)
6. Optional: recompute hash and compare to `content_hash`
---
## 16. File Deliverables
| File | Purpose |
|------|---------|
| `include/starkernel/capsule.h` | CapsuleDesc, CapsuleDirHeader, flags, enums |
| `include/starkernel/capsule_run.h` | CapsuleRunRecord for DoE logging |
| `src/starkernel/capsule/capsule_validate.c` | Validation function |
| `src/starkernel/capsule/capsule_birth.c` | Birth protocol implementation |
| `src/starkernel/capsule/capsule_run.c` | DoE run logging |
---
## 17. Summary
| Principle | Enforcement |
|-----------|-------------|
| One truth per VM | Birth only accepts (p) capsules |
| DOMAIN is Mama-only | Construction concept, never visible to babies |
| PERSONALITY is baby-only | Executed INIT result, immutable identity |
| (e) capsules are Mama-only | Never executed by or visible to baby VMs |
| Content-addressed | `capsule_id == content_hash` |
| Names don't exist at runtime | Symbolic resolution at build time only |
| Revocation is permanent | REVOKED overrides ACTIVE, capsule stays enumerable |
| DoE doesn't change truth | Workloads are (e), logged separately |
| Mama sees all | Children see one capsule, read-only |
| No implicit sharing | Twins exist because Mama chose same capsule |
| Provenance is mandatory | PARITY:BIRTH and PARITY:RUN logged |
---
## 18. Next Steps
1. **Implement `capsule.h`** — structs, flags, macros
2. **Implement `capsule_validate()`** — freestanding, no libc
3. **Build tooling** — generate static capsule directory from `.4th` files
4. **Integrate with birth** — extend `sk_vm_bootstrap` to accept capsule pointer
5. **Parity logging** — extend parity subsystem for BIRTH/RUN records
---
## 19. Gap Analysis (Resolved)
### 18.1 Payload Encoding
**Decision:** Raw UTF-8 FORTH source for Phase A.
- Simple, debuggable, portable
- Hash computed over raw bytes
- Tokenization/compilation is premature optimization
- Future phases may add `CAPSULE_FLAG_TOKENIZED` if needed
### 18.2 VM ID Allocation
Mama maintains a VM registry:
```c
typedef struct {
uint32_t vm_id; /* assigned at birth, immutable */
uint64_t birth_capsule_id;
uint64_t birth_timestamp_ns;
uint32_t state; /* EMBRYO, LIVE, DEAD */
uint32_t flags;
} VMRegistryEntry;
```
- VM ID 0 is reserved (mama)
- IDs are sequential (1, 2, 3, ...)
- ID is never reused within a session
### 18.3 Birth Failure Handling
If init execution fails:
1. VM never transitions to LIVE state
2. VM is marked STILLBORN
3. Parity log records failure:
```
PARITY:BIRTH_FAILED vm_id=N capsule_id=X error=E partial_dict_hash=H
```
4. Partial state is **not** observable by other VMs
5. Mama may retry with same or different capsule
### 18.4 Run Log Storage
`CapsuleRunRecord` entries stored in:
**Phase A:** Fixed ring buffer in kernel BSS
```c
#define MAX_RUN_RECORDS 1024
CapsuleRunRecord run_log[MAX_RUN_RECORDS];
uint32_t run_log_head;
```
**Phase B:** Append-only log, optionally persisted to block device
### 18.5 Atomicity of birth_count
**Rule:** Only mama increments `birth_count`.
Since mama is single-threaded (or serialized), no atomic operations needed in Phase A. Phase B (SMP mama) would require `__atomic_fetch_add`.
### 18.6 xxHash64 Implementation
Freestanding xxHash64 required:
```
include/starkernel/xxhash64.h
src/starkernel/hash/xxhash64.c
```
Public domain implementation (~200 lines). Must be:
- No libc dependency
- Deterministic across platforms
- Seedable (seed = 0 for capsule hashing)
---
## 20. Implementation Checklist
- [ ] `include/starkernel/capsule.h` — structs, flags, macros
- [ ] `include/starkernel/capsule_run.h` — CapsuleRunRecord
- [ ] `include/starkernel/xxhash64.h` — hash function
- [ ] `src/starkernel/hash/xxhash64.c` — freestanding xxHash64
- [ ] `src/starkernel/capsule/capsule_validate.c` — validation
- [ ] `src/starkernel/capsule/capsule_birth.c` — birth protocol
- [ ] `src/starkernel/capsule/capsule_run.c` — DoE run logging
- [ ] `tools/mkcapsule.c` — build tool to generate capsule directory
- [ ] Extend parity subsystem for BIRTH/BIRTH_FAILED/RUN records
- [ ] VM registry in mama
---
*This design will still make sense 20 years from now.*
+700
View File
@@ -0,0 +1,700 @@
# StarForth Ontology, Taxonomy, and Lexicon
**Version**: 1.1
**Date**: 2026-08-13
**Purpose**: Formal conceptual framework for precise academic discourse
---
## I. ONTOLOGY (Conceptual Framework)
### 1.1 Core Concepts
```
StarForth Adaptive Runtime System
├── Execution Metrics
│ ├── Execution Frequency (primary measurable)
│ ├── Temporal Decay (derived quantity)
│ └── Transition Probability (derived quantity)
├── Adaptive Mechanisms
│ ├── Frequency-Based Caching
│ ├── Window-Based Inference
│ └── Decay-Based Pruning
├── Convergence Properties
│ ├── Deterministic Behavior
│ ├── Steady-State Equilibrium
│ └── Variance Reduction
└── Analysis Frameworks
├── Dynamical Systems View
├── Statistical Inference View
└── Control Theory View
```
### 1.2 Conceptual Relationships
```
METAPHORICAL MAPPING (Thermodynamics → Execution):
Thermal Energy ≈ Execution Frequency (measurable count)
Heat Dissipation ≈ Exponential Decay (time-based reduction)
Thermal Equilibrium ≈ Steady-State Convergence (stable metrics)
Temperature ≈ Normalized Frequency Rank
Cooling Rate ≈ Decay Coefficient (λ)
LITERAL IMPLEMENTATIONS (No Metaphor):
Frequency Counter → Integer increment on execution
Decay Function → f(t) = f₀ * e^(-λt)
Hot-Words Cache → Top-K frequency-sorted entries
Rolling Window → Circular buffer of execution records
ANOVA Test → Statistical variance analysis (Levene's test)
```
---
## II. TAXONOMY (Hierarchical Classification)
### 2.1 Adaptive Runtime Components
```
1. Measurement Layer
1.1 Execution Frequency Tracking
└── Dictionary Entry Counter (per-word increment)
1.2 Temporal Recording
└── Rolling Window of Truth (circular buffer)
1.3 Transition Tracking
└── Word-to-Word Transition Matrix
2. Transformation Layer
2.1 Decay Models
2.1.1 Linear Decay (Loop #3)
└── Δf = -k * Δt
2.1.2 Exponential Decay (Loop #6 Inference)
└── f(t) = f₀ * e^(-λt)
2.2 Normalization
2.2.1 Frequency Ranking
2.2.2 Probability Calculation
3. Inference Layer
3.1 Window Width Inference (Loop #5)
3.1.1 Variance Analysis (Levene's Test)
3.1.2 Binary Search (Variance Inflection Point)
3.2 Decay Slope Inference (Loop #6)
3.2.1 Exponential Regression
3.2.2 Least Squares Fitting
4. Actuation Layer
4.1 Hot-Words Cache (Loop #1)
4.1.1 Frequency-Based Sorting
4.1.2 Top-K Selection
4.1.3 Fast-Path Dictionary Lookup
4.2 Speculative Execution (Loop #4)
4.2.1 Transition Probability Calculation
4.2.2 Prefetch Decision
5. Coordination Layer
5.1 Heartbeat System (Loop #7)
5.1.1 Time-Driven Tick Generation
5.1.2 Loop Orchestration
5.1.3 Adaptive Tick Rate
```
### 2.2 Feedback Loop Taxonomy
```
Feedback Loops (7 Total)
├── Positive Loops (Amplifying)
│ ├── Loop #1: Execution Heat Tracking
│ │ └── More executions → Higher rank → More cache hits → More executions
│ └── Loop #4: Pipelining Metrics
│ └── More transitions → Better prediction → More prefetch hits
├── Negative Loops (Stabilizing)
│ ├── Loop #3: Linear Decay
│ │ └── High frequency → Faster decay → Lower frequency → Slower decay
│ ├── Loop #5: Window Width Inference
│ │ └── High variance → Smaller window → Lower variance
│ └── Loop #6: Decay Slope Inference
│ └── Unstable metrics → Steeper decay → Faster stabilization
├── Neutral Loops (Monitoring)
│ └── Loop #2: Rolling Window History
│ └── Execution occurs → Record in window → Historical data available
└── Meta-Loop (Adaptive Coordination)
└── Loop #7: Adaptive Heartbeat
└── Stable system → Slower ticks → Less overhead
```
### 2.3 Convergence Taxonomy
```
Convergence Properties
├── Determinism
│ ├── Algorithmic Determinism (same inputs → same outputs)
│ ├── Temporal Determinism (time-invariant steady state)
│ └── Statistical Determinism (0% variance)
├── Steady-State Characteristics
│ ├── Fixed Point (attractors in phase space)
│ ├── Periodic Behavior (limit cycles)
│ └── Chaotic Behavior (sensitive dependence - avoided)
└── Variance Metrics
├── Inter-Run Variance (across multiple executions)
├── Intra-Run Variance (within single execution)
└── Temporal Variance (across time windows)
```
---
## III. LEXICON (Precise Definitions)
### 3.1 Core Terms (Alphabetical)
**Adaptive Heartbeat**
*Definition*: Time-driven coordination mechanism that orchestrates feedback loop execution at dynamically-adjusted intervals.
*Formal*: Thread T executing `vm_tick()` at frequency f_tick, where f_tick ∈ [f_min, f_max] adapts based on system stability.
*Measurement*: Tick period in nanoseconds (configurable: HEARTBEAT_TICK_NS).
*Category*: Coordination mechanism.
**Attractor**
*Definition*: Stable equilibrium point or region in phase space toward which execution trajectories converge.
*Formal*: Fixed point x* where F(x*) = x* for dynamical system x_{t+1} = F(x_t).
*Measurement*: Coordinates in (window_size, decay_slope, variance) space.
*Category*: Dynamical systems concept.
**Decay Coefficient (λ)**
*Definition*: Rate parameter controlling exponential reduction in execution frequency over time.
*Formal*: λ in f(t) = f₀ * e^(-λt), units of [1/time].
*Measurement*: Derived via exponential regression; stored as Q48.16 fixed-point.
*Category*: Transformation parameter.
**Deterministic Convergence**
*Definition*: Property whereby repeated executions of identical workloads produce statistically indistinguishable steady-state metrics.
*Formal*: ∀ executions i,j: |metric_i - metric_j| / σ < ε, where ε → 0 as t → ∞.
*Measurement*: Coefficient of variation CV = σ/μ → 0%.
*Category*: Convergence property.
**Execution Frequency**
*Definition*: Count of times a dictionary entry has been executed since VM initialization, optionally adjusted by decay.
*Formal*: f = Σ executions - ∫ decay(t) dt.
*Measurement*: Unsigned 64-bit integer (`uint64_t execution_heat`).
*Category*: Primary measurable quantity.
*Note*: "Heat" is metaphorical naming; actual quantity is frequency.
**Exponential Decay**
*Definition*: Mathematical function modeling reduction in execution frequency proportional to current value.
*Formal*: f(t) = f₀ * e^(-λt), where f₀ is initial frequency.
*Measurement*: Applied periodically by heartbeat system.
*Category*: Transformation function.
*Metaphor*: Analogous to radioactive decay or thermal dissipation.
**Hot-Words Cache**
*Definition*: Fixed-size array storing pointers to the K most frequently executed dictionary entries for O(1) lookup acceleration.
*Formal*: Cache C = {e_1, e_2, ..., e_K} where f(e_i) ≥ f(e_{i+1}) ∀ i.
*Measurement*: Cache size K (configurable: HOTWORDS_CACHE_SIZE).
*Category*: Frequency-based optimization.
**Levene's Test**
*Definition*: Non-parametric statistical test for homogeneity of variance across groups.
*Formal*: H₀: σ₁² = σ₂² = ... = σ_k² (variances equal).
*Measurement*: F-statistic with p-value threshold (typically 0.05).
*Category*: Statistical inference method.
*Application*: Used to detect variance changes when adjusting window size.
**Phase Space**
*Definition*: Multi-dimensional coordinate system where each axis represents a system state variable.
*Formal*: Space S = {(w, λ, σ²) | w ∈ ℕ, λ ∈ ℝ⁺, σ² ∈ ℝ⁺}.
*Measurement*: Coordinates: (window_size, decay_slope_Q48, variance_Q48).
*Category*: Dynamical systems representation.
**Rolling Window of Truth**
*Definition*: Circular buffer recording recent execution history for deterministic metric seeding.
*Formal*: Buffer B[i] = word_id at execution event i mod |B|.
*Measurement*: Buffer size (configurable: ROLLING_WINDOW_SIZE = 4096).
*Category*: Temporal recording mechanism.
*Purpose*: Ensures identical initial conditions for reproducibility.
**Steady-State Equilibrium**
*Definition*: Condition where adaptive system metrics stabilize within bounded oscillation.
*Formal*: ∃ t_0: ∀ t > t_0, |x(t) - x*| < δ for small δ.
*Measurement*: Variance CV < 0.1% over 1000-tick window.
*Category*: Convergence property.
*Metaphor*: Analogous to thermodynamic equilibrium.
**Thermodynamic Metaphor**
*Definition*: Conceptual mapping between thermodynamic quantities and execution metrics.
*Mapping*:
- Heat ↔ Execution Frequency
- Temperature ↔ Normalized Rank
- Cooling ↔ Exponential Decay
- Equilibrium ↔ Steady State
*Category*: Conceptual framework (not literal physics).
*Warning*: Must qualify as metaphor in academic writing.
**Transition Probability**
*Definition*: Conditional probability that word B is executed immediately after word A.
*Formal*: P(B|A) = count(A→B) / count(A).
*Measurement*: Stored as Q48.16 fixed-point in transition matrix.
*Category*: Derived metric for speculative execution.
**Variance Inflection Point**
*Definition*: Window size w* where variance begins to increase when window shrinks below w*.
*Formal*: w* = arg min_w { Var(w) | w < w_current }.
*Measurement*: Found via binary search with Levene's test.
*Category*: Inference target.
*Purpose*: Optimal window size for stable metrics.
**Word Transition**
*Definition*: Sequential execution of word B immediately following word A in threaded code.
*Formal*: Event (A, B, t) where A executes at time t and B at time t+ε.
*Measurement*: Recorded in transition_metrics.history[] circular buffer.
*Category*: Execution event.
---
### 3.2 Avoid/Deprecated Terms
| ❌ Avoid | ✅ Use Instead | Reason |
|---------|---------------|--------|
| "Physics-based" | "Thermodynamically-inspired metaphor" | Not literal physics |
| "Execution heat" (in formal writing) | "Execution frequency with decay" | "Heat" is metaphorical |
| "Temperature" | "Normalized frequency rank" | No actual thermal quantity |
| "Quantum-inspired" | N/A | No quantum mechanics involved |
| "AI-driven" | "Statistically-inferred" | No neural networks or ML |
| "Learning" | "Adaptive inference" | Not machine learning |
| "Training" | "Convergence to steady state" | Not supervised learning |
---
## IV. MATHEMATICAL FORMALISM
### 4.1 Execution Frequency Evolution
**Discrete-time update**:
```
f[t+1] = f[t] + Δexec[t] - Δdecay[t]
where:
Δexec[t] = 1 if word executed at tick t, else 0
Δdecay[t] = λ * f[t] * Δt (exponential decay approximation)
```
**Continuous-time model**:
```
df/dt = r(t) - λf(t)
where:
r(t) = execution rate [executions/second]
λ = decay coefficient [1/second]
Solution:
f(t) = e^(-λt) * [f₀ + ∫₀ᵗ r(τ)e^(λτ) dτ]
```
### 4.2 Hot-Words Cache Selection
**Cache membership criterion**:
```
e ∈ Cache ⟺ rank(e) ≤ K
where:
rank(e) = |{e' ∈ Dictionary : f(e') > f(e)}| + 1
K = cache size (constant)
```
### 4.3 Window Width Inference
**Objective function**:
```
w* = arg min { Var(w) : w ∈ [w_min, w_current] }
Subject to:
Levene(w, w_current) → F-statistic
p-value(F) < α (typically α = 0.05)
```
### 4.4 Decay Slope Inference
**Exponential regression**:
```
Given: {(t_i, f_i)}_{i=1}^N from rolling window
Model: f(t) = f₀ * e^(-λt)
Log-transform: ln(f) = ln(f₀) - λt
Least squares:
λ* = arg min Σ [ln(f_i) - (ln(f₀) - λt_i)]²
```
### 4.5 Transition Probability
**Maximum likelihood estimate**:
```
P(B|A) = count(A→B) / count(A)
where:
count(A→B) = # times B executed immediately after A
count(A) = # times A executed
```
### 4.6 Convergence Metric
**Coefficient of Variation**:
```
CV = σ / μ
where:
σ = √(Var[metric]) = standard deviation
μ = E[metric] = mean
Convergence achieved when: CV → 0
```
---
## V. RELATIONSHIP DIAGRAM
### 5.1 Component Dependencies
```
┌─────────────────────────────────────────────────────────────┐
│ Execution Event │
│ (word executed) │
└──────────────────────┬──────────────────────────────────────┘
│
┌───────────┴───────────┐
│ │
▼ ▼
┌────────────────┐ ┌──────────────────┐
│ Frequency │ │ Rolling Window │
│ Increment │ │ Recording │
│ (Loop #1) │ │ (Loop #2) │
└────────┬───────┘ └─────────┬────────┘
│ │
│ │
▼ ▼
┌────────────────┐ ┌──────────────────┐
│ Decay │ │ Inference │
│ Application │ │ Engine │
│ (Loop #3,#6) │ │ (Loop #5,#6) │
└────────┬───────┘ └─────────┬────────┘
│ │
└───────────┬────────────┘
│
▼
┌───────────────────────┐
│ Hot-Words Cache │
│ Reorganization │
└───────────┬───────────┘
│
▼
┌───────────────────────┐
│ Optimized Lookup │
│ (O(1) cache hit) │
└───────────────────────┘
```
### 5.2 Feedback Loop Interactions
```
Execution → Frequency ↑ → Cache Rank ↑ → Lookup Speed ↑ → More Execution
▲ │
│ │
└─────────────── (Positive Feedback) ────────────┘
Frequency ↑ → Decay ↑ → Frequency ↓ → Decay ↓ → Frequency Stabilizes
▲ │
│ │
└──── (Negative Feedback) ────┘
Variance ↑ → Window Shrink → Variance ↓ → Window Stable
▲ │
│ │
└──── (Negative Feedback) ──┘
```
---
## VI. ONTOLOGICAL COMMITMENTS
### 6.1 Foundational Assumptions
1. **Frequency as Proxy for Importance**
- Assumption: Frequently executed code is more important to optimize
- Justification: Empirical validation (Zipf's law in execution patterns)
2. **Decay Models Temporal Relevance**
- Assumption: Recent executions are more relevant than distant past
- Justification: Locality of reference (temporal locality principle)
3. **Determinism Through Convergence**
- Assumption: Adaptive systems can converge to deterministic steady states
- Justification: Fixed-point theorems for contractive mappings
4. **Statistical Inference Validity**
- Assumption: Execution patterns are statistically analyzable
- Justification: Central Limit Theorem for large sample sizes
### 6.2 Scope Limitations
**What This Ontology DOES Cover**:
- ✓ Execution frequency measurement and decay
- ✓ Adaptive caching and inference mechanisms
- ✓ Dynamical systems characterization
- ✓ Statistical convergence properties
**What This Ontology DOES NOT Cover**:
- ✗ Actual thermodynamic processes (metaphor only)
- ✗ Machine learning or neural networks (no training)
- ✗ Quantum computing (no quantum effects)
- ✗ Biological neural systems (no biomimicry)
---
## VII. USAGE GUIDELINES
### 7.1 Academic Writing
**In Abstracts/Titles**: Use precise, non-metaphorical terms
```
✅ "Thermodynamically-Inspired Adaptive Runtime"
❌ "Physics-Based Virtual Machine"
```
**In Technical Sections**: Qualify metaphors explicitly
```
✅ "We employ a thermodynamic metaphor where execution frequency
is treated as 'heat' that dissipates over time..."
❌ "The physics model applies heat decay..."
```
**In Formalism**: Use mathematical definitions, not analogies
```
✅ "Frequency evolves as f(t) = f₀ * e^(-λt)"
❌ "Heat cools exponentially like in Newton's law"
```
### 7.2 Code Comments
**In Source Code**: Use concrete terms from lexicon
```c
// ✅ GOOD: Increment execution frequency counter
entry->execution_heat++;
// ❌ BAD: Increase temperature of word
entry->execution_heat++; // heat up!
```
### 7.3 Presentation/Talks
**Slides**: Use metaphor for intuition, then formalize
```
Slide 1: "Think of execution frequency like heat..."
Slide 2: "Formally: f(t) = f₀ * e^(-λt)"
```
---
## VIII. REFERENCES
### 8.1 Foundational Concepts
- **Dynamical Systems**: Strogatz, S. (2015). *Nonlinear Dynamics and Chaos*
- **Control Theory**: Åström, K. & Murray, R. (2008). *Feedback Systems*
- **Statistical Inference**: Casella, G. & Berger, R. (2002). *Statistical Inference*
- **Exponential Decay**: Standard mathematical function (e^(-λt))
### 8.2 Related Work
- **Trace-based JIT**: Bolz et al. (2009). "Tracing the Meta-Level: PyPy's Tracing JIT Compiler"
- **Adaptive Systems**: Garlan et al. (2004). "Rainbow: Architecture-Based Self-Adaptation"
- **Forth Optimization**: Ertl, M.A. (1996). "Stack Caching for Interpreters"
---
## IX. STADIUM / FABRIC VOCABULARY (v1.1 extension)
**Added 2026-08-13**, closing a real, acknowledged documentation-debt item: the physics
vocabulary built on top of the original heat/decay/inference model above (patron, mass,
density, Stadium, and the Kconfig knob names built on them) accumulated across
`FABRIC-0.md`/`FABRIC-1.md` without an explicit statement, in one place, that these are named
analogies — the same discipline Section III already applies to "heat," extended to the
newer terms. Definitions below are drawn directly from `FABRIC-0.md`'s own DECIDED sections
(cited inline), not invented here.
### 9.1 The metaphor, restated for this layer
```
METAPHORICAL MAPPING (Stadium → Runtime state):
Stadium ≈ Bounded live-state region (a venue, not a building)
Patron ≈ A live thing occupying floor space in that region
Mass ≈ How much floor space a patron occupies
Density ≈ How much heat a patron holds per unit of floor space
Warehouse ≈ What is not currently live (Artemis, the dictionary at rest)
Utility ≈ Infrastructure beside the Stadium, never inside it (framebuffer)
LITERAL IMPLEMENTATIONS (No Metaphor):
Stadium → A fixed-size array of cells, index-linked, allocated at boot
Patron → A cell (or linked run of cells) with a nine-field header
Mass → Integer count of cells a patron occupies (≥ 1)
Density → heat ÷ mass, a derived ratio, not stored
K → Σ execution_heat_q48 across live entries, conserved at 1.0
```
The Stadium metaphor is a **venue**, not a building: §2 (`FABRIC-0.md`) is explicit that a
patron *arrives and departs* — the Stadium holds what is live, not everything that exists.
That is the load-bearing distinction between "Stadium" and "warehouse" below, and it is why
the dictionary-at-rest is warehouse, not Stadium, even though every dictionary entry is
*eligible* to become a patron.
### 9.2 Lexicon additions (alphabetical, same format as Section III)
**Cell**
*Definition*: The Stadium's fixed-size unit of storage — a patron header, or a continuation
cell owned by exactly one patron.
*Formal*: A closed, two-constructor sum type: `Cell = Header | Continuation(owner)`, fixed
at build time (`FABRIC-0.md` §3).
*Measurement*: Proposed size 64 bytes (`FABRIC-0.md` §23.3, **not yet built** — see
`FABRIC-1.md`'s §23.4 #2 closure: the one real patron kind actually shipped, Hermes
messages, did not adopt this cell shape).
*Category*: Storage unit.
*Warning*: Not a pointer target — links between cells are Stadium-array indices, never
addresses (`FABRIC-0.md` §3, §13).
**Code field**
*Definition*: A closed-enumeration behaviour tag stored in a patron header, dispatched by
the engine rather than branched on by kind.
*Formal*: `behaviour ∈ {MIGRATE, DELIVER, EXPIRE, COOL, ...}` — a fixed datatype, not an
open extension point (`FABRIC-0.md` §3, §13, §18.3).
*Category*: Dispatch mechanism.
*Note*: This is deliberately **the entire type system** — the engine never asks what kind
of patron it holds, only what its code field says to do (`FABRIC-0.md` §3).
**Density**
*Definition*: A patron's heat share divided by its mass — heat per unit of occupied
Stadium floor space.
*Formal*: `density(p) = heat(p) / mass(p)`, derived, never stored (`FABRIC-0.md` §19.2).
*Measurement*: Used for ranking and admission (denser evicts sparser), not computed by a
separate scheduler — it is read, not decided (`FABRIC-0.md` §19.3).
*Category*: Derived quantity.
*Warning*: This is the one place the thermodynamic metaphor is literal in form (quantity
per unit volume) even though heat itself remains metaphorical.
**K (conservation constant)**
*Definition*: The sum of every live entry's normalised heat share.
*Formal*: `K = Σ execution_heat_q48`, held at `Q48_ONE` (1.0) by construction — verified by
`vm_physics_conserved()` (`capsule_vm_physics.c`).
*Measurement*: A correctness check, not a diagnostic — it is 1.0 by construction and can
never itself signal system state (`FABRIC-0.md` §14, §19.1). Entropy over the heat
*distribution* is the actual instrument; K is not.
*Category*: Invariant.
*Warning*: Not an occupancy ratio (`Σmass / capacity`) — that definition was considered and
explicitly rejected because it contradicts the already-implemented, tested mechanism
(`FABRIC-0.md` §19.1).
**Mass**
*Definition*: The number of Stadium cells a patron occupies — its footprint.
*Formal*: Integer ≥ 1, an entry field independent of heat; never enters K
(`FABRIC-0.md` §3, §19.2).
*Category*: Structural quantity, not conserved.
*Note*: A large patron is not "referenced" from the Stadium — it simply occupies more
cells. By-reference storage is reserved for things outside the Stadium entirely
(`FABRIC-0.md` §19.3, §23.1).
**Mass conservation vs. heat conservation**
*Definition*: Two independent invariants that must not be confused. Heat (K) is conserved
globally and moved by traffic. Mass is conserved *per patron* during a residency —
identity and mass never change as a side effect of use — but is not a fleet-wide summed
invariant the way heat is (`FABRIC-0.md` §24.2).
**Patron**
*Definition*: A live thing occupying a Stadium cell or linked run of cells — the unit the
Stadium actually holds.
*Formal*: One structure, no variants, no type field — identified by its code field's
behaviour, not by a kind tag (`FABRIC-0.md` §3, §13).
*Measurement*: Nine header wires: identity, heat, TTL, pin, link, code field, mass,
payload, contains (`FABRIC-0.md` §3, with amendments cited in place).
*Category*: The Stadium's sole occupant type.
*Known kinds (as of this writing)*: words (dictionary — fully migrated and live via
`stadium_word_dispatch()`, `FABRIC-0.md` §16.3/§17.7), blocks (Artemis), messages (Hermes — shipped with a
**different** shape than this header, see `FABRIC-1.md`'s §23.4 #2 closure), VMs (Tripod,
`FABRIC-0.md` §20), dirty events (console, `FABRIC-0.md` §17.5). Five kinds, not four — an
earlier draft undercounted (`FABRIC-0.md` §1).
**Reap**
*Definition*: The event that ends a patron's residency — not destruction, a return of its
cells and remaining heat to the pool.
*Formal*: Consumed on delivery (messages), on redraw (dirty events), on cooling below floor
(VMs), or on eviction by a denser newcomer (`FABRIC-0.md` §17.2, §17.5, §19.3).
*Category*: Lifecycle event.
**Stadium**
*Definition*: A single, bounded region of memory outside any VM, holding everything
currently live.
*Formal*: A fixed-size array of cells, index-linked (not pointer-linked), allocated at
boot before any VM exists, not part of the heap (`FABRIC-0.md` §2, §17.6).
*Measurement*: Per-VM quota over a single global cell pool, elastic under pressure,
arbitrated by Hera (`FABRIC-0.md` §22) — not per-VM physical regions.
*Category*: The bounded live-state substrate this whole vocabulary describes.
*Warning*: "Live" is the operative word — the Stadium holds what is live, not everything
that exists. A dictionary word sitting cold and unexecuted is warehouse, not Stadium, even
once the dictionary itself migrates into this representation (`FABRIC-0.md` §2, §16.3).
**Warehouse**
*Definition*: What lies beneath the Stadium — storage for things not currently live.
*Formal*: Artemis (block storage) and the resting dictionary are warehouse; the Stadium is
the floor above it (`FABRIC-0.md` §17.5's three-category taxonomy: warehouse / Stadium /
utility).
*Category*: Storage layer, outside the engine's live-state model.
**Utility**
*Definition*: External infrastructure the Stadium consumes but never contains — beside the
building, not inside or beneath it.
*Formal*: The framebuffer and devices generally; the engine never learns about a utility's
existence, which is what makes it a boundary rather than an exception (`FABRIC-0.md` §17.5).
*Category*: Third taxonomy category, coordinate with warehouse and Stadium, not a
sub-case of either.
### 9.3 Kconfig knob names built on this vocabulary
Per Bob's own flag: code, Kconfig, and documentation terminology must not drift apart.
The table below is the current, verified wiring status of every `STADIUM_*` Kconfig
symbol — confirmed by direct source grep during the `FABRIC-1.md` closure work this
vocabulary accompanies, not assumed from the symbol's name or Kconfig help text alone.
| Kconfig symbol | Vocabulary term | Wiring status (2026-08-13) |
|---|---|---|
| `STADIUM_MAX_VM_COUNT` | Patron (VM kind) population bound | **Live** — read in `stadium.c`, `stadium_words.c`, `capsule_birth.c` |
| `STADIUM_MEMORY_PERCENT` | Stadium capacity sizing | **Live** — read in `stadium.c` |
| `STADIUM_WORD_HEAT_QUANTUM` | Heat (word-patron admission) | **Live** — read in `stadium_words.c` |
| `STADIUM_WORD_COOL_RATE_Q48` | Heat decay rate (word patrons) | **Live** — read in `stadium_words.c` |
| `STADIUM_CONTAINS_DEPTH_MAX` | `contains` wire depth cap (§3, §8) | **Declared, not yet enforced** — reap-gating enforcement is item 3.5's scope, not implemented (`FABRIC-1.md` §A item 5.3 area; not a bug, a known sequencing gap) |
| `STADIUM_CAPACITY_TICK` | Capacity-loop cadence, distinct from heat's (§22.4) | **Dead** — declared with the correct intended default (1000, matching §22.4's precedent) but never read by any code path; only appearance beyond its own default is a compile-time `> 0` static assertion in `stadium.h`. Found and reported during this session's §12 Q5 closure (`FABRIC-1.md`) — `vm_physics_heartbeat_tick()` still literally shares `HEARTBEAT_INFERENCE_FREQUENCY` instead |
This table is a snapshot, not a promise — it will drift the moment any of these symbols is
wired or unwired. Re-verify against source before relying on it for anything past a casual
read; do not assume "Live" here still means live without checking.
---
## X. VERSION HISTORY
**v1.1** (2026-08-13):
- Added Section IX: Stadium/Fabric vocabulary (patron, mass, density, Stadium, warehouse,
utility, K, cell, code field, reap) as named analogies, matching Section III's existing
discipline for the original heat/decay vocabulary
- Added a Kconfig knob-name-to-concept mapping table with verified (not assumed) wiring
status, per Captain Bob's 2026-08-04 flag that code/Kconfig/documentation terminology
must not drift apart
**v1.0** (2025-12-13):
- Initial ontology, taxonomy, and lexicon
- Formal mathematical definitions
- Relationship diagrams
- Usage guidelines
---
## XI. ACKNOWLEDGMENTS
This ontology provides the formal conceptual framework for the StarForth project.
It aims to eliminate ambiguity and enable precise academic discourse while
acknowledging the metaphorical nature of certain conceptual mappings.
**License**: See ./LICENSE
+587
View File
@@ -0,0 +1,587 @@
# LithosAnanke Roadmap
**Branch:** `master` (post-split: this repo's `master` is the sole LithosAnanke production
line; the old `lithosananke` branch belonged to the pre-split combined monorepo and no
longer exists here — see `.claude/CLAUDE.md`'s "On the branch topology" note)
**Current:** M7.1 in progress — see `FABRIC-0.md`/`FABRIC-1.md`/`FABRIC-2.md` for real status
---
## 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.
**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.
- **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; 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
on the real SER5 via its real UEFI; POST + `ok>`; the amd64 **RDRAND** backend
(`rng: backend = rdrand`) serves live entropy; and a real Zuse identity is minted on a
second thumbdrive in real hardware and re-attaches. Two 16 GB sticks available: one to boot
the image, one to mint the Zuse user.
- **v2.4.0 — aarch64 bare metal.** Raspberry Pi 5 (orderable ~Mon 2026-08-31). Gate: boots
on the real board, aarch64 peripheral-RNG backend live, Zuse mint/attach on real media.
- **v2.5.0 — all three bare metal.** Adds Milk-V (riscv64) to the above; the Zkr (RNDR)
backend live. This is the full "same story on all three arches on real silicon" cut.
**Beyond v2.5.0 — Zynq FPGA is the next big milestone, after a hardening phase.** Once the
three general-purpose boards close, the next major transfer is to a **Zynq (AMD Xilinx)
FPGA** SoC — the step where the battle-tested amd64/aarch64/riscv64 story rides on
configurable silicon. That is a genuinely bigger milestone than any single prior board: an
FPGA demands its platform be carried forward rather than ported with trivial
architecture-delta work, and it reshapes the hardware story (soft/hard CPU cores, PL fabric,
non-standard memory map, custom peripherals). Expect a new even-major line for it once the
coloring-in period below lands.
**The FPGA creates a three-product split, decided 2026-08-29.** The Zynq is not just a fourth
platform to port to — it is the pivot that forces the project to separate into three
distinct products, each with its own host, delivery, and proof character. This is the neat
partition the FPGA's configurable-silicon nature makes possible and demands:
1. **Hosted StarForth** — the hosted/interpreted StarForth product that already exists
(3-arch acceptance-tested; runs StarForth hosted on an OS). Its delivery is the Forth
+ VM + capsule semantics as a portable, embeddable interpreted runtime.
2. **A full StarshipOS** — the standalone operating system built on LithosAnanke
(LithosAnanke → StarshipOS). Its delivery is a self-booting OS on general-purpose
silicon (the SER5/RasPi/Milk-V line already covers this).
3. **Hardware steady-state machinery with sealed executions, HOL-proven** — the
FPGA-native product: hardware-enforced sealed executions and steady-state machinery
whose guarantees are machine-checked in a proof assistant (HOL). This is the product only
configurable silicon can honor — secret/hardware-boundary enforcement and formally
verified behavior carried in silicon rather than software. Its delivery is the bitstream +
the HOL proof artifacts (not just a port of the OS).
The coupling is the point: the FPGA is where product 3 is born, and product 3's existence is
what cleanly separates 1 from 2 from 3. Product 1 ships hosted on an OS; product 2 ships as
an OS on general silicon; product 3 ships as proven hardware. Anything that straddles those
boundaries after this decision is a conscious product-line choice, not an accident of history.
Scoping of each product's concrete gates, and the new even-major line that carries products 1
through 3 as separate tracks, is defined at v2.5.0 close / during the coloring-in phase.
(This complements, and does not retract, the existing "hardware bare-metal release" policy
above.)
**IP framing of the three products — 3 patent applications + 3 marks, decided 2026-08-29.**
The three products are treated as **three patent applications**, each matched to a trademark,
with one caveat about the pre-existing provisional:
- **Patent 1 + mark StarForth.** The hosted/interpreted StarForth runtime product.
- **Patent 2 + marks StarshipOS + LithosAnanke.** The full standalone OS
(LithosAnanke → StarshipOS). (Noted as "StarshopOS" in the 2026-08-29 decision words;
canonical repo spelling and mark is **StarshipOS** — confirmed as the literal mark
to register.)
- **Patent 3 + mark Compudynamics.** The Zynq hardware steady-state machinery with sealed
executions, HOL-proven — anchored by the physics-adaptive runtime.
**Driving deadlines (why "no hurry" is wrong for #1 and #2), clarified 2026-08-29.** Two
clocks bind the near term, independent of product #3 and the FPGA:
- **#1 — the provisional will expire before conversion.** Its priority claim is time-boxed:
if the non-provisional isn't filed claiming benefit before the provisional's window lapses
(provisional filed ~Dec 2025 → window ~Dec 2026), the provisional's priority is lost and
the same subject matter cannot be re-staked by refiling thereafter. So #1 is on a hard
clock regardless of the December scope.
- **#2 — full StarshipOS is the December deliverable.** This is what "keeps the flagship
covered": the standalone-OS product (and, pending confirmation below, possibly the vehicle
that converts #1's provisional) must deliver by December.
- **Open point to resolve with counsel:** is **#2's** application (or the December delivery)
the **conversion vehicle** for #1's provisional — i.e., does it claim benefit from the
#1 provisional and thereby secure what would otherwise lapse — or is #1 converted by a
filing separate from #2? Captured as a decision record; no filing made.
**Relationship to the existing provisional (must be reconciled, not assumed).** The repo
already carries a **"Patent pending"** USPTO **provisional filed December 2025** for the
physics-grounded self-adaptive runtime (the Compudynamics adaptive runtime), sourced in
`docs/patent/` and referenced in `README.md`. Compudynamics is therefore already staked as
the brand of that foundation. Open points to resolve with counsel before any filing, so
nothing is invented or double-filed: is patent 3 the continuation/refinement of that
provisional (its HOL hardware realization) or a fresh application? Are the three product
applications additive to, or folding in, the Dec-2025 provisional? This record states intent;
it does not file, claim, or draft legal text.
**Before the FPGA — "coloring in", decided 2026-08-29.** The period between the all-three
bare-metal cut (v2.5.0) and starting the Zynq is finishing/hardening work that thickens the
shape of what already exists rather than adding new silicon. This is not idle time; it is
the point where v2.5.0's real-hardware story is made production-honest before the FPGA asks
to carry it further. Concrete items to define during it (draft scope, to be firmed at v2.5.0
close): Zynq-preflight robustness of the USB BOT/xHCI and block paths; live-entropy and
Zuse-cert hardening on the three real boards; SMP/multi-core bring-up and IRQ routing on real
ASICs (parked in the HAL notes); driver set expansion beyond the three boards before
committing an FPGA port; and whatever v2.5.0's real-board validation surfaces. As boards
land and the v2.5.0 gates close, this list is edited down to the concrete coloring-in punch
list, and the Zynq becomes the even-major target after it.
QEMU remains the zero-degradation reference on every line; each `X.Y.0` must reproduce the
QEMU acceptance story (`POST 1012/0/0` + `ok>`, block-fence Zuse true, backend entropy
non-deterministic) on its board before it closes.
---
## Milestone Overview
```
M0 UEFI Boot ████████████████████ COMPLETE
M1 PMM ████████████████████ COMPLETE
M2 VMM ████████████████████ COMPLETE
M3 IDT ████████████████████ COMPLETE
M4 APIC ████████████████████ COMPLETE
M5 Timer ████████████████████ COMPLETE
M6 Heap ████████████████████ COMPLETE
M7 VM Parity ████████████████████ COMPLETE
M7.1 Capsules ████████████████░░░░ LIVE — see FABRIC-0.md/FABRIC-1.md/FABRIC-2.md
M8 REPL ░░░░░░░░░░░░░░░░░░░░ OBSOLETE — see FABRIC-0.md §25.5 item 4.4
M9 Block I/O ░░░░░░░░░░░░░░░░░░░░ PLANNED
M10 Networking ░░░░░░░░░░░░░░░░░░░░ FUTURE
```
---
## Phase 1: Boot Foundation (M0-M6)
### M0: UEFI Boot + Serial Output
**Goal:** Kernel prints "LithosAnanke booting..." to serial
**Deliverables:**
- [x] UEFI loader (`uefi_loader.c`)
- [x] BootInfo handoff (memory map, ACPI, framebuffer)
- [x] ExitBootServices
- [x] Serial console output
**Validation:**
```
StarKernel UEFI Loader
Loading...
RAW SERIAL UP
```
---
### M1: Physical Memory Manager
**Goal:** Track and allocate physical page frames
**Deliverables:**
- [x] Bitmap allocator
- [x] Parse UEFI memory map
- [x] `pmm_alloc_pages()` / `pmm_free_pages()`
- [x] Statistics reporting
**Validation:**
```
PMM initialized.
PMM statistics:
Total pages: 249450
Free pages : 247921
```
---
### M2: Virtual Memory Manager
**Goal:** 4-level paging, identity + higher-half mapping
**Deliverables:**
- [x] PML4 → PDPT → PD → PT setup
- [x] CR3 switch
- [x] `vmm_map()` / `vmm_unmap()`
- [x] Self-test
**Validation:**
```
VMM initialized (mapped RAM, CR3 switched)
VMM self-test: mapped OK at 0xffff800000000000
```
---
### M3: Interrupt Descriptor Table
**Goal:** CPU exceptions and IRQ handling
**Deliverables:**
- [x] 256-entry IDT
- [x] ISR stubs (asm)
- [x] Exception handlers (div-by-zero, page fault, etc.)
- [x] IRQ routing framework
**Validation:**
```
IDT installed.
```
---
### M4: APIC Timer
**Goal:** Local APIC initialization and timer IRQs
**Deliverables:**
- [x] Local APIC enable
- [x] APIC timer configuration
- [x] Spurious interrupt vector
**Validation:**
```
APIC: init...
APIC enabled (SIVR=0xFF).
APIC: init done
```
---
### M5: Timer Calibration
**Goal:** Accurate time measurement
**Deliverables:**
- [x] TSC frequency detection
- [x] HPET calibration (when available)
- [x] PM Timer fallback
- [x] Relative vs. absolute trust levels
**Validation:**
```
Timer: init...
Timer: trust=1 (0=NONE,1=REL,2=ABS), TSC=0 Hz
Timer: init done
```
---
### M6: Kernel Heap
**Goal:** `kmalloc()` / `kfree()` working
**Deliverables:**
- [x] Heap initialization
- [x] Allocation tracking
- [x] Statistics
**Validation:**
```
Kernel heap initialized.
Heap statistics:
Total bytes: 16777176
Free bytes: 16777176
```
---
## Phase 2: VM Integration (M7)
### M7: VM Parity Validation
**Goal:** StarForth VM boots with reproducible dictionary hash
**Deliverables:**
- [x] VMHostServices abstraction
- [x] VM arena allocation (5 MB)
- [x] FORTH-79 word registration (295 words)
- [x] Parity checkpoint logging
- [x] Heartbeat thread start
**Validation:**
```
VM: bootstrap parity...
[HAL][host] VMHostServices table registered
VM arena allocated: 0xffff900000000000 (5 MB)
Registering FORTH-79 Standard word set...
PARITY:M7.1a word_count=295 here=0x30 latest_id=294 hash=0x684bbf2fa1d96d55
PARITY:OK
VM: parity bootstrap complete
Starting heartbeat...
APIC Timer: started
```
**Commit:** `6f350bc` — M7: StarForth VM integration with parity validation
---
## Phase 3: Capsule Architecture (M7.1)
### M7.1: Init Capsule System
**Goal:** Content-addressed, immutable init capsules for VM birth
**Status:** Design Complete (see [M7.1.md](M7.1.md)) — this section predates the actual
capsule/Tripod/Stadium work and is not current. **See `FABRIC-0.md`/`FABRIC-1.md` for real
status** (per `.claude/CLAUDE.md`'s standing redirect) — Tripod (Hera/Hermes/Artemis),
word-level ACL Phases 1–7, and the Stadium physics-economy migration are all live, not just
designed. Most recently: item 4.6 (Artemis's block-heat arena migrated to Stadium
admission-on-allocate) verified passing its 30-rep stress campaign on all three
architectures 2026-08-18, after fixing a quota-grant ordering bug (`capsule_birth.c`) that
had made every admission fail; see `FABRIC-1.md` Section H. The same pass also fixed a
long-standing aarch64-only crash on `BYE` (wrong PSCI conduit — SMC has nothing to answer it
under AAVMF/no-EL3 boot; switched to HVC), see `FABRIC-1.md` Section I.
**Core Concepts:**
| Concept | Description |
|---------|-------------|
| **DOMAIN** | Mama-only construction space — never visible to babies |
| **PERSONALITY** | Baby-only identity — result of executing (p) INIT |
| **(p) Production** | Truth-bearing capsules that birth VMs |
| **(e) Experiment** | Mama-only workloads for DoE |
**Birth Protocol:**
1. Mama selects one production `(p)` capsule by content hash
2. Mama validates eligibility (ACTIVE, PRODUCTION, not REVOKED)
3. Mama allocates new VM
4. INIT blocks copied to execution window (RAM blocks 0–2047)
5. INIT blocks executed sequentially
6. Execution window cleared
7. VM begins life with PERSONALITY imprinted
8. Mama logs `PARITY:BIRTH vm_id=N capsule_id=X mode=p ...`
9. Mama increments `capsule.birth_count`
**Deliverables:**
- [ ] `CapsuleDesc` struct (64 bytes, cache-aligned)
- [ ] `CapsuleDirHeader` struct
- [ ] xxHash64 implementation (freestanding)
- [ ] `capsule_validate()` function
- [ ] Birth protocol (`PARITY:BIRTH` logging)
- [ ] DoE run logging (`CapsuleRunRecord`)
- [ ] `mkcapsule` build tool
**Key Design Decisions:**
- Content-addressed: `capsule_id == content_hash`
- (p) Production vs (e) Experiment modes
- One truth per VM — no shared/implicit base INITs
- Mama holds all truths — (e) capsules never touch babies
- Twins/variants are just VMs with same/similar capsules
---
## Phase 4: Interactive Forth (M8)
> **⚠ OBSOLETE (2026-08-11).** This entire M8 section is superseded by
> `FABRIC-0.md` §25.5 item 4.4 (REPL). The console/REPL design has moved past
> what's scoped here — do not use this section as a source for REPL or
> CANVAS design decisions. Kept in place for history only.
### M8: REPL + Interactive Forth
**Status:** Scoped 2026-08-11 (this section). Substrate (interrupt-driven keyboard
input, glyph rendering) already complete via Stadium's Console fabric work
(FABRIC-0.md §25.5 items 4.3.5–4.3.7f) — see "What's already done" below. This
scoping pass covers what's left: wiring the existing keyboard-event stream into
the REPL's character input path.
**Goal:** Type Forth at the kernel *keyboard* (not just serial), get `ok` prompt.
The REPL already works interactively over serial today (`sk_repl_run()`,
`src/starkernel/repl.c`) — that is not what this milestone adds. What's missing
is a second input source feeding the same loop.
**What's already done, read from the code before scoping this, not assumed:**
- Interrupt-driven keyboard input on all three architectures (amd64 i8042,
aarch64/riscv64 virtio-keyboard-pci + GIC/PLIC), converged behind one shared
interface: `KEY-EVENT ( -- keycode pressed -1 | 0 )` in
`src/word_source/keyboard_words.c`, `keycode` in the Linux input keycode
namespace. FABRIC-0.md items 4.3.5–4.3.5f, verified live on all three
architectures with real keypresses.
- Glyph rendering, both paths: the stroke-drawn font (`TEXT`, capsule-based,
113-glyph v1 repertoire, FABRIC-0.md 4.3.6–4.3.6g) and TrueType (`TTF-TEXT`,
proportional spacing via real `hmtx` advance widths, FABRIC-0.md 4.3.7–4.3.7f).
Neither is required for the REPL text itself, though — see the VT100 finding
below.
- **The REPL's on-screen text already renders independently of both of the
above.** `console_fb_init()` (`src/starkernel/hal/console.c`) calls
`vt100_init()`, which mirrors every `console_putc()`/`console_puts()` call to
the framebuffer via the baked-in `font_8x16.c` raster font. This is *already
live* — the REPL prompt and its output appear on screen today, serial-driven,
no CANVAS/glyph-capsule dependency. This confirms §27.6's mockup: a fixed
VT100 REPL strip (already working) plus a separate large CANVAS region above
it (still unsized — see the last deliverable below).
**What's actually missing, confirmed by reading the code, not assumed:**
`src/starkernel/repl.c`'s `sk_readline()` reads characters via
`console_getc()` (`src/starkernel/hal/console.c`), which is serial-UART-only —
it never looks at `KEY-EVENT`. Separately, the FORTH-79 words `KEY`/`?TERMINAL`
(`src/word_source/io_words.c:84-108`, shared/vendored source) are dead in the
kernel build: `KEY` calls libc `getchar()`, which the kernel shim
(`src/starkernel/vm/host/shim.c:1206`) hardcodes to return `-1` (EOF);
`?TERMINAL` is a literal stub that always returns false. Neither path has ever
been wired to a real keyboard.
**Deliverables:**
- [ ] Minimal scancode→ASCII layout: a small lookup table (US QWERTY,
unshifted/shifted rows only — no dead keys, no locale switching) driven
by `KEY-EVENT`'s Linux-namespace keycodes, plus shift-state tracking
(`KEY_LEFTSHIFT`/`KEY_RIGHTSHIFT` press/release) since `KEY-EVENT` itself
carries no modifier state today (confirmed by reading
`keyboard_words.c` — deferred at 4.3.5f, not implemented anywhere).
- [ ] Merge keyboard into `console_getc()`'s poll: extend it (or add a second
function `sk_readline()` polls alongside it) to also drain `KEY-EVENT`
and translate presses to ASCII via the layout table above. **Serial stays
a co-equal input source, not replaced** — the entire acceptance/DoE
harness injects FORTH commands over the serial socket
(`socat ... UNIX-CONNECT:$SERIAL_SOCK`) and greps the serial log for
`ok>`; breaking that path breaks `make qemu`, `DOE_INJECT`, and every
screendump verification technique used throughout 4.3.x. Both sources
feed the same byte stream into the existing `sk_readline()` — no new
line-editing code needed, backspace/echo already work
(`sk_readline()`, `repl.c:83-141`).
- [ ] Wire `KEY`/`?TERMINAL` (`io_words.c`) to the same merged source under
`#ifdef __STARKERNEL__`, for FORTH-79 compliance — any capsule calling
these directly (not just the REPL) should get real input too, not a
permanently-EOF stub. Small addition once the merge above exists.
- [ ] CANVAS viewport sizing (§27.6's explicit deferral to this milestone): the
large graphics region above the VT100 REPL strip needs an actual
scrollable framebuffer viewport size decided (e.g. 640×480, per the
original mockup) — layout/presentation, not input plumbing; do this
last, after keyboard input works, since it doesn't block interactivity.
**Explicitly out of scope for this milestone (minimal bar, matches "Line
editing (backspace, minimal)"):** extended (`0xE0`-prefixed) i8042 scancodes,
virtio-input autorepeat (`value=2`) as actual character repeat, arrow-key
cursor movement/history, non-US keyboard layouts. Deferred to a later item if
ever needed — not blocking "type Forth at the kernel, get `ok`."
**Validation:**
```
LithosAnanke v0.3.0
ok 1 2 + .
3 ok
```
(via a real keypress on all three architectures, same live-keypress
verification standard FABRIC-0.md 4.3.5f already established — not a
serial-injected synthetic test, though serial injection must still work
identically afterward.)
---
## Phase 5: Persistence (M9)
### M9: Block Storage
**Goal:** Read/write blocks to disk
**Deliverables:**
- [x] Block device driver — implemented via `virtio_blk.c` (virtio block device), not the
AHCI/SATA driver originally scoped here; see `.claude/CLAUDE.md`'s M9 note.
- [x] Block device abstraction — `block_subsystem.c`'s unified LBN address space, backend-agnostic
across RAM/RAMDRIVE/DISK/USB via the `blkio_dev` vtable.
- [x] `BLOCK` / `BUFFER` / `UPDATE` / `FLUSH` words — live, `src/word_source/block_words.c`.
- [ ] Persistent dictionary — no evidence found of word definitions surviving reboot; not
independently verified as done, left unchecked rather than assumed from the rest of this
list being live.
---
## Future Milestones
### M10: Networking
- VirtIO-net driver
- TCP/IP stack (minimal)
- DHCP client
### M11: Process Model
- Forth tasks
- Scheduling
- IPC
### M12: Self-Hosting
- Compile Forth on LithosAnanke
- Edit/assemble/link cycle
---
## Validation Commands
```bash
# Build
make -f kernel/Makefile ARCH=amd64 STARFORTH_ENABLE_VM=1
# Run QEMU
make -f kernel/Makefile ARCH=amd64 STARFORTH_ENABLE_VM=1 qemu
# Clean
make -f kernel/Makefile ARCH=amd64 clean-kernel
```
---
## Branch Relationship
```
master (post-split: sole LithosAnanke production line — the old
lithosananke/master two-branch topology no longer exists in this repo)
└── M0–M7 complete, M7.1 live (capsules / Tripod / Stadium / ACL)
```
---
## Success Criteria
LithosAnanke is successful when:
1. **M7 Parity** — VM dictionary hash reproducible across boots
2. **M7.1 Capsules** — Birth protocol enforced, provenance logged
3. ~~**M8 REPL** — Interactive Forth at bare metal~~ — superseded, this criterion is now met
via Stadium's Console fabric work (`FABRIC-0.md` §25.5 item 4.4), not a standalone M8; see
the OBSOLETE banner above Phase 4
4. **M9 Persistence** — State survives reboot
---
*The foundation is laid. The necessity is clear.*
+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.
@@ -0,0 +1,178 @@
# Hosted StarForth — 3-Architecture Acceptance Test
**Date**: 2026-07-24 (original), corrected 2026-08-02
**Branch**: `master`
**Commits**: `edced063` (original block-subsystem fix), `4485c38` (doc +
3 clang-surfaced bug fixes), `e287334` (asm register-reuse fix), this pass
(`riscv64-clang` Makefile target)
---
## What this verifies
The **hosted** StarForth VM (`make`, not `Makefile.starkernel`) is a static
binary. This test confirms it starts cleanly — reaches the `ok>` REPL prompt
with no errors — on all three architectures the project targets:
- **amd64** — native execution on the build host
- **aarch64** — cross-compiled, run under QEMU user-mode emulation
- **riscv64** — cross-compiled, run under QEMU user-mode emulation
This is distinct from the kernel (`Makefile.starkernel`) 3-arch acceptance
test documented in
[`../amd64-isr-fix/README.md`](../amd64-isr-fix/README.md), which boots a
full UEFI bare-metal image under system-mode QEMU. This test only exercises
the hosted Linux binary, using QEMU **user-mode** emulation to run a
foreign-architecture static binary directly on the x86_64 host — no kernel,
no UEFI firmware, no system emulation.
---
## Prerequisites
Cross toolchains, QEMU user-mode emulation, and clang (needed for the
riscv64 leg — see below):
```bash
sudo apt-get install -y \
gcc-aarch64-linux-gnu \
gcc-riscv64-linux-gnu \
clang-18 lld-18 \
qemu-user
```
`qemu-user` alone is enough — the binaries built below are statically
linked (`-static`), so the non-static `qemu-<arch>` emulators run them
directly with no sysroot. (The `qemu-user-static` package, if you see it
referenced elsewhere, provides statically-linked *emulators*; it's not
required just because the *guest* binary is static.)
---
## Invocation note
None of the three binaries accept a `-c "<script>"` flag — earlier
revisions of this doc showed one, but `src/cli.c`/`src/main.c` have never
had one in this repo's history. Pipe the script via stdin with `-s`
(script/silent mode) instead. Startup always runs the full internal POST
test suite before the REPL takes stdin, so expect substantial test-suite
output before your script's result appears at the very end.
---
## amd64 — native
```bash
make clean && make
echo "1 2 + . BYE" | ./build/amd64/standard/starforth -s
```
Expect `3 Goodbye!` at the end of the output, then a clean exit. No
emulation needed — this is the build host's native architecture.
---
## aarch64 — cross-compile + QEMU user-mode
```bash
make rpi4-cross
echo "1 2 + . BYE" | qemu-aarch64 ./build/raspi/fastest/starforth -s
```
`make rpi4-cross` cross-compiles with `aarch64-linux-gnu-gcc`
(`ARCH=raspi`, `TARGET=fastest`, `-static`); see `Makefile:547-554`. The
output binary is statically linked, so `qemu-aarch64` can execute it
directly with no sysroot.
---
## riscv64 — cross-compile + QEMU user-mode
```bash
make riscv64-clang
echo "1 2 + . BYE" | qemu-riscv64 ./build/riscv64/fastest/starforth -s
```
`make riscv64-clang` cross-compiles with `clang-18` (`ARCH=riscv64`,
`TARGET=fastest`, `-static`); see `Makefile:566-579`. **This leg must use
clang, not GCC**: `riscv64-linux-gnu-gcc` fails to build this tree
(`nanosleep` visibility failure under `-std=c99`, unrelated to any project
change — root cause not yet chased down, see
`docs/working/archive/session-logs/2026-07-24-punch-list.md` item #3). The
target's `CFLAGS` deliberately does not reuse the Makefile's
`$(BASE_CFLAGS)` (which hardcodes `-std=c99`) — clang needs `-std=c11
-pthread` here instead, plus `-fuse-ld=lld` in `LDFLAGS` (folding it into
`CC` makes clang reject it as an unused argument during `-c`/compile-only
invocations under `-Werror`).
---
## Pass criteria
For each architecture:
1. Build completes with no errors.
2. `blk_subsys_init` succeeds — no `"Failed to initialize block subsystem"`
error. (This was broken on every architecture, including amd64, prior to
`edced063` — see below.)
3. The internal POST suite runs (substantial log output — expected, not a
failure by itself) and the piped `1 2 + . BYE` script prints `3 Goodbye!`
at the end.
---
## Background: the block-subsystem bug this depends on
Before `edced063`, the hosted VM could not start on **any** architecture on
this branch. `src/main.c` allocated a hardcoded `blk_ram[1024 * 1024]`
buffer, stale relative to `BLK_RAM_BLOCKS=2080` (`include/block_subsystem.h`)
— a deliberate redesign that moved the RAM/ramdrive boundary to physical
block 2080 (user-visible LBN 2048), per `BLK_FORTH_SYS_RESERVED=32`. The
kernel-side buffer in `src/starkernel/vm/bootstrap/sk_vm_bootstrap.c` already
derived its size correctly from the shared constant; `main.c` was the one
remaining hardcoded holdout. The fix:
```c
#include "block_subsystem.h"
...
static uint8_t blk_ram[BLK_RAM_BLOCKS * BLK_FORTH_SIZE]; /* was: blk_ram[1024 * 1024] */
```
This is why the acceptance test above is meaningful on this branch
specifically: it did not pass before this fix, on any of the three
architectures.
---
## Background: the riscv64 clang build (2026-08-02 correction)
The riscv64 command above was broken in this doc from 2026-07-24 until this
correction — `docs/working/archive/session-logs/2026-07-24-punch-list.md`
(item #2) recorded that the working clang invocation had been found and
fixed in the old pre-split monorepo's `master` branch's `.claude/CLAUDE.md`,
but the correction was never copied into this doc. Verifying it end-to-end
for this correction surfaced that the clang build also needs three small
source fixes from that same punch list (item #1), previously ported to the
old monorepo's `master` but not to this line:
- `src/math_portable.c` — `-100LL << 16` is UB (shifting a negative value)
under `-Wshift-negative-value`; changed to `-(100LL << 16)`.
- `src/physics_pipelining_metrics.c` — removed dead `q48_mul_q48()`
(`-Wunused-function` under clang; GCC doesn't catch this by default).
- `src/word_source/editor_words.c` — removed dead `set_scr()` (same reason).
The punch list's highest-priority item — a genuine SIGSEGV-causing
register-reuse hazard in `include/vm_asm_opt_riscv64.h`'s `vm_pop_asm`/
`vm_rpop_asm` (clang's register allocator can pick the same register for a
memory operand referenced both before and after a write-back, corrupting
the result; GCC happens not to hit it) was *not* required just to get this
acceptance test's `-O3` build to compile and pass, but has since been
fixed too (commit `e287334`) — and turned out not to be merely a latent
risk: it was silently corrupting 10 `CASE.*` control-flow tests under this
exact build (`955`/`10 failed` → `965`/`0 failed`, `ALL IMPLEMENTED TESTS
PASSED!`, with nothing else changed).
Item #3 of the same punch list — no `make` target for this leg, only the
long manual invocation — is also resolved as of this pass: `make
riscv64-clang` wires in the same recipe verified above directly into the
Makefile.
+323
View File
@@ -0,0 +1,323 @@
# Kernel Command-Line Arguments (KernelArgs)
**Branch:** `lithosananke`
**Status:** Design complete — awaiting implementation
## Motivation
Memory sizing and runtime behavior are currently compile-time constants:
```c
#define KERNEL_HEAP_SIZE (2ULL * 1024 * 1024 * 1024) /* kernel_main.c */
.space 0x200000 /* kernel_entry.S */
```
This means tuning stack depth or heap size for a given machine requires a
full rebuild. The JVM model (`-Xmx`, `-Xss`) is the right analogy: bake in
safe defaults, allow runtime overrides via boot-time command line. The same
mechanism also lets the kernel self-start the DoE and control log verbosity
without relying on external socat injection from the Makefile scripts.
## UEFI Mechanism
`EFI_LOADED_IMAGE_PROTOCOL->LoadOptions` is a UCS-2 string passed to the
EFI application by the firmware or boot manager. It is available to the
loader before `ExitBootServices()` at no extra cost — the loader already
holds `loaded_image` (see `uefi_loader.c:160`). We convert it to ASCII
(simple 8-bit truncation; all valid switches are ASCII) and parse it into
a `KernelArgs` struct that travels to the kernel via `BootInfo`.
## New Files
| File | Purpose |
|------|---------|
| `include/starkernel/kernel_args.h` | `KernelArgs` struct + default macros |
| `src/starkernel/boot/cmdline.c` | UCS-2→ASCII conversion + tokeniser + parser |
| `include/starkernel/cmdline.h` | Parser API (`cmdline_parse`) |
## Changed Files
| File | Change |
|------|--------|
| `include/starkernel/uefi.h` | Add `KernelArgs args` + `void *kernel_stack_base` + `uint64_t kernel_stack_size` to `BootInfo` |
| `src/starkernel/boot/uefi_loader.c` | Call `cmdline_parse(loaded_image->LoadOptions, ...)`, allocate dynamic stack, fill `BootInfo` |
| `src/starkernel/arch/amd64/kernel_entry.S` | Read `boot_info->kernel_stack_base`; fall back to BSS stack if zero |
| `src/starkernel/kernel_main.c` | Use `boot_info->args.heap_size`; honour `run_doe` and `log_level` |
| `Makefile.starkernel` | Pass `--doe` via OVMF boot entry args; remove socat EXEC-DOE injection |
## `KernelArgs` Struct
```c
/* include/starkernel/kernel_args.h */
#define KARGS_DEFAULT_STACK_SIZE (2ULL * 1024 * 1024) /* 2 MB */
#define KARGS_DEFAULT_HEAP_SIZE (2ULL * 1024 * 1024 * 1024) /* 2 GB */
#define KARGS_DEFAULT_LOG_LEVEL 1 /* info */
#define KARGS_DEFAULT_RUN_DOE 0
typedef struct {
/* Memory */
uint64_t stack_size; /* --stack=<size> e.g. 2M 4M 8M */
uint64_t heap_size; /* --heap=<size> e.g. 2G 4G */
/* Runtime behaviour */
int run_doe; /* --doe flag, default off */
int log_level; /* --log-level=<level> debug=0 info=1 warn=2 error=3 */
/* Extension: raw unparsed cmdline preserved verbatim (null-terminated) */
char raw[512];
} KernelArgs;
```
All fields have explicit defaults. Unknown switches are ignored and
appended to `raw` so future kernels can act on them while old kernels boot
safely.
## `BootInfo` Changes
Two fields are added **directly** to `BootInfo` (not nested inside
`KernelArgs`) so `kernel_entry.S` can read them at fixed, known offsets
without any C header dependency:
```c
typedef struct {
EFI_MEMORY_DESCRIPTOR *memory_map;
UINTN memory_map_size;
UINTN memory_map_descriptor_size;
EFI_RUNTIME_SERVICES *runtime_services;
void *acpi_table;
FramebufferInfo framebuffer;
UINT8 uefi_boot_services_exited;
/* NEW — kernel stack (loader-allocated; zero = use BSS fallback) */
void *kernel_stack_base; /* low address of allocation */
uint64_t kernel_stack_size;
/* NEW — all other parsed arguments */
KernelArgs args;
} BootInfo;
```
Keeping `kernel_stack_base` / `kernel_stack_size` at the top-level avoids
having to compute a nested struct offset inside assembly.
## Assembly Trampoline (`kernel_entry.S`)
```asm
kernel_main:
/* rdi = BootInfo* */
mov rax, [rdi + BOOT_INFO_STACK_BASE_OFFSET]
test rax, rax
jnz .dynamic_stack
lea rax, [rip + g_kernel_stack_top] /* BSS fallback */
jmp .stack_ready
.dynamic_stack:
mov rcx, [rdi + BOOT_INFO_STACK_SIZE_OFFSET]
add rax, rcx /* top = base + size */
.stack_ready:
and rax, -16
sub rax, 8
mov rsp, rax
xor rbp, rbp
jmp kernel_main_impl
```
`BOOT_INFO_STACK_BASE_OFFSET` and `BOOT_INFO_STACK_SIZE_OFFSET` are defined
as integer constants in a small `boot_info_offsets.h` that is included by
both C (to `_Static_assert` correctness) and the assembly file.
## Parser (`cmdline.c`)
```c
void cmdline_parse(const CHAR16 *load_options, BootInfo *boot_info);
```
Steps:
1. Convert UCS-2 → ASCII into a local 512-byte buffer (truncate at 511).
2. Copy verbatim into `boot_info->args.raw`.
3. Fill defaults into `boot_info->args`.
4. Tokenise on spaces; for each token:
- `--stack=<N>[KMG]` → `boot_info->kernel_stack_size` + `args.stack_size`
- `--heap=<N>[KMG]` → `args.heap_size`
- `--doe` → `args.run_doe = 1`
- `--log-level=<s>` → `args.log_level` (debug/info/warn/error → 0-3)
- anything else → silently ignored (forward-compat)
5. Allocate stack via `BS->AllocatePages(AllocateAnyPages, EfiLoaderData, pages)`.
Store result in `boot_info->kernel_stack_base`.
Size suffix parser handles `K`/`M`/`G` (case-insensitive); bare numbers are
treated as bytes.
## Loader Integration
In `uefi_loader.c`, after the existing `HandleProtocol` call for
`loaded_image` and before `ExitBootServices`:
```c
cmdline_parse(loaded_image->LoadOptions, &boot_info);
```
`boot_info` is already on the loader stack and passed to the kernel entry;
no structural change needed.
## `kernel_main.c` Integration
```c
/* Heap — use runtime size if provided */
uint64_t heap_sz = boot_info->args.heap_size
? boot_info->args.heap_size
: KARGS_DEFAULT_HEAP_SIZE;
kmalloc_init(heap_sz);
/* DoE — self-start if requested */
#ifdef STARFORTH_ENABLE_VM
if (boot_info->args.run_doe) {
vm_interpret(mama, "EXEC-DOE");
}
#endif
```
Log level feeds into the existing `LOG-DEBUG` / `LOG-INFO` etc. word
infrastructure via a new `sk_set_log_level(int)` call.
## Makefile Integration
The `qemu` target currently injects `EXEC-DOE` via socat after detecting
`ok>`. With `--doe` supported natively:
```makefile
# Pass --doe via OVMF boot entry OptionalData
QEMU_KERNEL_ARGS ?= --doe --log-level=info --stack=2M --heap=2G
```
The ISO build embeds these in the UEFI boot entry so the kernel receives
them from firmware. The socat injection dance is removed. The `SK_CMD`
compile-time flag remains available for one-off testing.
## Passing Args in QEMU
OVMF reads boot entry `OptionalData` from the UEFI variable store. The
cleanest approach for the Makefile: use a small Python/shell script to
write the boot entry with the desired args into `OVMF_VARS.fd` before
launching QEMU. Alternatively, `efibootmgr` syntax baked into the ISO's
`startup.nsh` script works with the OVMF shell.
## `REBOOT` FORTH Word
### Purpose
Allows a running kernel to set new boot flags and immediately cold-reset.
The canonical use case:
```forth
S" --doe --log-level=debug" REBOOT
```
On the next boot the loader finds the one-shot NVRAM variable, uses it
instead of `LoadOptions`, and clears it so subsequent boots revert to
normal.
### Stack effect
```
REBOOT ( addr len -- )
```
Takes a counted string (same convention as `S"`) containing the new
argument line.
### Interpret-only guard
`REBOOT` is a normal (non-immediate) word that checks compilation state at
the top of its body. Attempting to include it in a `:` definition produces
a `-14` throw (`interpretation semantics only`) **at definition time**, not
at run time — which is the correct FORTH-standard behaviour.
```c
static void word_reboot(VM *vm) {
if (vm->compiling) {
vm_throw(vm, -14); /* interpretation semantics only */
return;
}
...
}
```
### NVRAM one-shot mechanism
When `REBOOT` executes it:
1. Pops `addr` / `len` from the data stack.
2. Validates length (`0 < len < KERNEL_ARGS_CMDLINE_MAX`); throws `-11` on
out-of-range.
3. Converts the string to UCS-2.
4. Reads `StarForthRebootTries` from NVRAM (default 0 if absent).
5. If `tries >= REBOOT_MAX_TRIES` (default **3**):
- Clears both `StarForthBootArgs` and `StarForthRebootTries` variables.
- Prints warning to serial: `REBOOT: max tries reached — dropping to REPL`.
- Returns normally (does **not** reset); execution continues at the REPL.
6. Otherwise: writes `StarForthBootArgs` and increments `StarForthRebootTries`,
then calls `RuntimeServices->ResetSystem(EfiResetCold, EFI_SUCCESS, 0, NULL)`.
This call does not return.
On the next boot, the loader checks for `StarForthBootArgs`:
- If found: copies value, **clears `StarForthBootArgs`** (one-shot), uses it
for parsing. `StarForthRebootTries` is left untouched by the loader — it
is only cleared by a successful REPL session (see below) or by the
max-tries guard above.
On reaching the REPL successfully, `kernel_main` clears `StarForthRebootTries`
to zero, resetting the counter for the next `REBOOT` invocation. This means
a clean interactive boot always resets the escape-hatch state.
### `SetVariable` failure path
If any `SetVariable` call returns a non-`EFI_SUCCESS` status:
- Print warning: `REBOOT: NVRAM write failed (<status>) — dropping to REPL`.
- Do **not** attempt `ResetSystem`.
- Return normally; execution continues at the REPL.
No infinite retry loop. One attempt, one warning, one escape to REPL.
### Hosted-VM stub
On the Linux-hosted build `RuntimeServices` is unavailable. The stub logs
the would-be args and sets `vm->halted = 1`:
```c
#ifndef STARFORTH_KERNEL
hal_console_puts("[REBOOT] hosted stub — would reboot with: ");
hal_console_write((const char *)VM_ADDR(vm, addr), (size_t)len);
hal_console_puts("\n");
vm->halted = 1;
#endif
```
### Registration
Registered alongside `BYE` / `WARM` / `COLD` in `system_words.c`:
```c
REGISTER_WORD("REBOOT", word_reboot, WORD_NORMAL);
```
---
## Extension Points
Adding a new switch requires:
1. A field in `KernelArgs` with a default macro.
2. One `else if` branch in `cmdline_parse()`.
3. One read site in the kernel (usually `kernel_main.c`).
No changes to `BootInfo`, `kernel_entry.S`, or the loader calling code.
## Invariants
- If `LoadOptions` is NULL or empty, all defaults apply — identical to
current behaviour. Existing QEMU launch commands continue to work.
- The BSS stack (`g_kernel_stack`) remains in the binary as a fallback.
It can be removed in a future cleanup once dynamic allocation is proven.
- `KernelArgs.raw` is always null-terminated; max 511 chars.
- Unknown arguments never cause a boot failure.
+18
View File
@@ -0,0 +1,18 @@
# docs/reference/kernel-args/
Design doc for kernel command-line arguments (`KernelArgs`).
- **[DESIGN.md](DESIGN.md)** — Design for runtime-configurable memory sizing
and behavior (heap size, stack depth, DoE self-start, log verbosity)
currently baked in as compile-time constants. Covers the UEFI
`EFI_LOADED_IMAGE_PROTOCOL->LoadOptions` mechanism, the `KernelArgs`
struct, `BootInfo` changes, the assembly trampoline
(`kernel_entry.S`), the `cmdline.c` parser, loader/`kernel_main.c`/
Makefile integration, passing args under QEMU, and the `REBOOT` FORTH
word (including its NVRAM one-shot mechanism and hosted-VM stub). Status:
design complete, awaiting implementation.
## See also
- [`docs/working/archive/historical/2026-10-lithosananke-orientation.md`](../README.md) — kernel branch overview.
- [Project root](../../../README.md)
+404
View File
@@ -0,0 +1,404 @@
# Word-Level ACL System
**Status:** Implemented through Phase 7 (LithosAnanke kernel parity) — independently verified
present in current `master` (`.claude/CLAUDE.md`'s Word-Level ACL System section). Phase 8
(PKI/thumbdrive minting) is the current open item.
**Target branch:** `master` — post-split, this repo's sole production line; there is no
separate `lithosananke` branch to reach parity with (see `.claude/CLAUDE.md`'s "On the branch
topology" note)
**Implementation files:** `capsules/ACL.4th`, `capsules/zuse.4th`, `src/word_source/acl_words.c`, `src/test_runner/modules/acl_words_test.c`
---
## Summary
### Overview
A word-level access control system for StarForth VMs, implemented as a
combination of a thin C infrastructure layer and a pure-FORTH policy capsule
(`ACL.4th`). The C layer provides four `DictEntry` fields for the hot path
(TTL counter + allow bit + mode + pin). All policy, all adaptive logic, all
inheritance rules live as colon definitions in `ACL.4th`. A bootstrap
superuser (`zuse.4th`) provides the sole authenticated escalation path.
---
### ACL Disposition — Three States
Each word in the dictionary has one of three enforcement dispositions:
| State | Behavior |
|-------|---------|
| `STRICT` | ACL re-checked on every execution |
| `TTL` | ACL cached; re-checked only when TTL counter reaches zero |
| `PINNED` | Current mode and decision frozen permanently; one-way ratchet |
---
### TTL — Statistically Adaptive
The TTL is not a fixed value. It is derived and continuously updated from
the existing SSM execution physics:
- **Heat** (`execution_heat`): hotter words earn longer TTLs; check cost
amortized over more executions
- **Rolling window**: sudden shift in caller pattern or access rate causes
TTL to shrink aggressively
- **Decay**: quiescent words pull their TTL back down so the next burst
re-validates early
- **Inference engine** (L5/L6): detects oscillating TTL and stabilizes it —
same CV threshold logic already used for window-width inference
`PINNED` is the asymptote: the adaptive process converges, you thumbtack
it, and the accumulator freezes permanently.
A FORTH security word selects enforcement mode per word:
```forth
' MY-WORD ACL-STRICT \ check every execution
' MY-WORD ACL-TTL-MODE \ use adaptive TTL
' MY-WORD ACL-PIN \ freeze — mode and decision immutable forever
```
---
### The Thumbtack / Pin Flag
`ACL-PIN ( xt -- )` is a one-way ratchet:
- Transitions: `STRICT → PINNED` or `TTL → PINNED` — never back
- Once pinned, `acl_mode` and `acl_ttl` are immutable for that VM context
- Any attempt to change a pinned word's ACL is silently ignored (or errors,
depending on policy)
- Kernel primitive words (`BIRTH`, `EXEC`, `BYE`, etc.) are pinned by Mama
at boot before the first `BIRTH` fires
---
### Inheritance at Birth
When a child VM is born:
```
acl_mode = parent->acl_mode \ policy propagates (STRICT or TTL)
acl_pinned = 0 \ always clear — child owns its own lock
acl_ttl = default \ reset; child has no execution history
acl_decision = ALLOW \ re-evaluated on first access
```
Key properties:
- **Pin is contextual, not viral.** A parent's pinned words do not force
the child to be pinned. The child inherits the mode as a starting point
and can tighten, relax, or re-pin freely.
- **Security lattice flows downward at birth only.** After birth, each VM's
ACL state is independent. A compromised child cannot bootstrap its way
back to Mama's pinned ACLs.
---
### Interpreter Hook — Two-Level Check
The C interpreter reads only two fields per `DictEntry`:
```c
if (vm->emergency_console) goto execute; /* 100% bypass — physical access */
if (entry->acl_ttl-- > 0) goto execute; /* TTL good — single decrement */
acl_recheck(vm, entry); /* TTL=0: call FORTH ACL-RECHECK */
if (!entry->acl_allow) goto reject;
```
**Hot path cost:** one decrement and a branch — essentially free.
**Cold path:** calls `ACL-RECHECK` in `ACL.4th`, which recomputes the
adaptive TTL, updates `acl_allow`, and resets the counter. The C side
never reasons about policy; it only reads the result.
---
### Two-Console Architecture
Two permanent, independent console layers exist at all times:
#### 1. Emergency Console (always present)
- `emergency_console` (`uint8_t`) flag in `VM` struct — C-only write; no
FORTH word sets it
- Checked first in the interpreter hot path — 100% ACL bypass
- `acl_recheck()` uses save/restore around it for re-entrancy protection
- `EMERGENCY_CONSOLE_ENABLED` Makefile build flag (default=1): when set to
0, strips interactive fallthrough; `vm_fault_handler()` weak symbol is
called instead (override for hardware reset, watchdog, debug probe)
- `BYE` returns to the emergency console
- Only `panic` kills the VM
- Prompt: `ok>`
#### 2. Zuse Console (omnipresent, awaiting authentication)
- `zuse_session` (`uint8_t`) flag in `VM` struct — C-only write; no FORTH
word sets it
- Prompt: `zuse)ok>`
- Full ACL bypass when a Zuse session is active
- Completely independent of `emergency_console` — setting one does not
affect the other
---
### Superuser: Zuse
Named for Konrad Zuse, pioneer of programmable computers. Zuse is the
bootstrap superuser: the sole entity that can own the zuse console and mint
user credentials.
- Defined in `capsules/zuse.4th`, loaded by `ACL.4th` at capsule boot
- Software-only for now (no thumbdrive); `zuse.4th` is a capsule citizen
from day one
- Zuse's words are pinned by `ACL-ZUSE-BOOT` at load time
- `ACL-BOOT` is called first, then `S" zuse.4th" EXEC` — both from within
`ACL.4th` itself (Block 2067)
- Future: replace with thumbdrive PKI (Ed25519 challenge-response);
`zuse.4th` becomes the bootstrapper that validates the physical drive
---
### CA Root
- Embedded in `ACL.4th` (Block 2066) as constants `ACL-CA-KEY-LO` /
`ACL-CA-KEY-HI`
- Capsule hash = root-of-trust fingerprint: any CA key change changes the
capsule hash and the birth protocol detects tampering
- Placeholder (zeros) until `tools/mkcapsule` embeds the real Ed25519 key
at build time
---
### Security Model / No-Security Condition
Security is opt-in via `init.4th`. The toggle is one commented-out line:
```forth
\ S" ACL.4th" EXEC
```
| `ACL.4th` | `zuse.4th` | Result |
|-----------|------------|--------|
| absent | absent | No security — open dev mode |
| present | absent | ACL enforced; emergency REPL locked until Zuse provisioned |
| present | present | Full lockdown; Zuse owns the zuse console |
Uncomment the line in `init.4th` to enable full lockdown. Leave it
commented out for development builds.
---
### Self-Activating Capsule
`ACL.4th` is self-activating — `init.4th` only needs one line:
```forth
S" ACL.4th" EXEC
```
Internal structure of `ACL.4th`:
- **Block 2066**: CA root placeholder constants (`ACL-CA-KEY-LO` /
`ACL-CA-KEY-HI`)
- **Block 2067**: Calls `ACL-BOOT` then `S" zuse.4th" EXEC`
`ACL-BOOT` stamps default ACL entries on all existing dictionary words.
After it runs, every subsequent `:` definition gets an ACL entry via the
defining-word hook. No word can exist without an ACL entry.
---
### Emergency Console — 100% Bypass
`vm->emergency_console` is set by the C layer when the physical `ok>` REPL
is active. It is checked first, before any ACL table lookup. Physical
presence always wins. The emergency console is never subject to ACL
constraints regardless of what `ACL.4th` defines.
The `EMERGENCY_CONSOLE_ENABLED=0` build flag strips the interactive
fallthrough for production or embedded builds, routing faults to
`vm_fault_handler()` instead.
---
### ACL.4th — Pure FORTH Implementation
The ACL table is a `CREATE`d FORTH array indexed by execution token (XT).
`'` (tick) gives the XT of any word; XT is just a cell value usable as a
table key.
Core words:
| Word | Stack | Description |
|------|-------|-------------|
| `ACL-ENTRY` | `( xt -- addr )` | O(1) table lookup by XT |
| `ACL-MODE@` | `( xt -- mode )` | read enforcement mode |
| `ACL-MODE!` | `( mode xt -- )` | set mode (no-op if pinned) |
| `ACL-PINNED?` | `( xt -- flag )` | test pin bit |
| `ACL-PIN` | `( xt -- )` | set pin — one-way, irreversible |
| `ACL-STRICT` | `( xt -- )` | set STRICT mode (no-op if pinned) |
| `ACL-TTL-MODE` | `( xt -- )` | set TTL mode (no-op if pinned) |
| `ACL-TTL@` | `( xt -- n )` | read current TTL counter |
| `ACL-TTL!` | `( n xt -- )` | write TTL counter |
| `ACL-ALLOW@` | `( xt -- flag )` | read cached allow/deny decision |
| `ACL-ALLOW!` | `( flag xt -- )` | write allow/deny decision |
| `ACL-INHERIT` | `( src dst -- )` | birth inheritance: copy mode, clear pin, reset ttl+decision |
| `ACL-RECHECK` | `( xt -- )` | adaptive TTL recomputation; updates allow + new TTL |
| `ACL-INIT-PRIMITIVES` | `( -- )` | bulk-initialize ACL entries for all existing dictionary words |
Example pin at boot in `init.4th`:
```forth
' BIRTH ACL-STRICT ' BIRTH ACL-PIN
' EXEC ACL-STRICT ' EXEC ACL-PIN
' BYE ACL-STRICT ' BYE ACL-PIN
```
---
### Bootstrap Sequence
```forth
\ In init.4th (one line, opt-in toggle — uncomment to enable):
S" ACL.4th" EXEC \ self-activating: calls ACL-BOOT then loads zuse.4th
\ ACL.4th Block 2067 does internally:
ACL-BOOT \ stamp default ACLs on all existing words
S" zuse.4th" EXEC \ load and pin Zuse's words
\ Subsequent capsule loads (e.g. doe.4th) get ACL entries via : hook
S" doe.4th" EXEC
```
Every `:` definition after `ACL-BOOT` runs creates its own ACL entry at
definition time via a hook in the defining word. No word is ever born
without an ACL entry.
---
### Capsule Namespace
| File | Role |
|------|------|
| `init.4th` | Mama VM personality (default); contains opt-in ACL toggle |
| `init-*.4th` | Alternate personalities |
| `doe.4th` | DoE experiment harness |
| `ACL.4th` | Word-level ACL subsystem — self-activating; loads `zuse.4th` |
| `zuse.4th` | Bootstrap superuser personality; words pinned by `ACL-ZUSE-BOOT` |
| `std-blob.4th` | Standard library layer (future) |
---
## Implementation Punch List
### Phase 1 — C Infrastructure (`master`) ✅ COMPLETE
- [x] Add `acl_ttl` (`uint32_t`) to `DictEntry` in `include/vm.h`
- [x] Add `acl_allow` (`uint8_t`) to `DictEntry`
- [x] Add `acl_mode` (`uint8_t`: STRICT=0 / TTL=1) to `DictEntry`
- [x] Add `acl_pinned` (`uint8_t`) to `DictEntry`
- [x] Default-initialize all four fields in `word_register()`:
`ttl=ACL_OPEN, allow=1, mode=TTL, pinned=0`
- [x] Add `emergency_console` (`uint8_t`) flag to `VM` struct
- [x] Add `zuse_session` (`uint8_t`) flag to `VM` struct
- [x] Insert two-level ACL check into interpreter loop in `vm.c`
- [x] Implement `acl_recheck()` in `vm.c` — calls FORTH `ACL-RECHECK` when
TTL=0; save/restore `emergency_console` for re-entrancy protection
- [x] Add `:` definition hook to create ACL entry for each newly defined word
### Phase 2 — C Primitive Words + `ACL.4th` Capsule (`master`) ✅ COMPLETE
- [x] 12 C primitive words in `src/word_source/acl_words.c`
- [x] `CREATE ACL-TABLE` sized to max dictionary entries
- [x] `ACL-ENTRY ( xt -- addr )` — XT-indexed O(1) lookup
- [x] Field accessors: `ACL-MODE@`, `ACL-MODE!`, `ACL-PINNED?`,
`ACL-TTL@`, `ACL-TTL!`, `ACL-ALLOW@`, `ACL-ALLOW!`
- [x] `ACL-PIN ( xt -- )` — one-way ratchet; no-op if already pinned
- [x] `ACL-STRICT ( xt -- )` — set STRICT mode; no-op if pinned
- [x] `ACL-TTL-MODE ( xt -- )` — set TTL mode; no-op if pinned
- [x] `ACL-INHERIT ( src-xt dst-xt -- )` — copy mode, clear pin,
reset ttl+decision
- [x] `ACL-RECHECK ( xt -- )` — recompute adaptive TTL from heat and rolling
window; update `acl_allow` and reset counter
- [x] `ACL-INIT-PRIMITIVES` — walk dictionary, create default entry per word
- [x] Pin privileged words: `BIRTH`, `EXEC`, `BYE`, and other Mama-only words
### Phase 3 — `capsules/ACL.4th` Self-Activating Capsule (`master`) ✅ COMPLETE
- [x] `ACL.4th` is self-activating: Block 2067 calls `ACL-BOOT` then
`S" zuse.4th" EXEC`
- [x] Block 2066: CA root placeholder constants (`ACL-CA-KEY-LO` /
`ACL-CA-KEY-HI`)
- [x] `ACL-BOOT` stamps default ACL entries on all existing dictionary words
- [x] `capsules/zuse.4th` loaded at end of `ACL.4th` capsule
- [x] Zuse's words pinned by `ACL-ZUSE-BOOT` at load time
### Phase 4 — `init.4th` Opt-In Toggle (`master`) ✅ COMPLETE
- [x] `init.4th` has one commented-out line as the opt-in toggle:
`\ S" ACL.4th" EXEC`
- [x] Uncomment to enable full lockdown; leave commented for dev/open mode
- [x] No other changes to `init.4th` required — `ACL.4th` is self-contained
### Phase 5 — POST Tests (`master`) ✅ COMPLETE
POST tests implemented in `src/test_runner/modules/acl_words_test.c`,
registered in `src/test_runner/test_runner.c`. 800/800 passing.
- [x] POST test: `ACL-PIN` is one-way — mode cannot change after pin set
- [x] POST test: inheritance — child entry has mode copied, pin cleared,
ttl and decision reset
- [x] POST test: emergency console bypass — `emergency_console=1` skips
ACL check entirely
- [x] POST test: STRICT mode — ACL re-evaluated on every execution
- [x] POST test: TTL hot path — TTL > 0 bypasses re-evaluation
- [x] POST test: adaptive accumulator — heat increase produces TTL increase
- [x] POST test: privileged words pinned at boot remain pinned after
`ACL-INIT-PRIMITIVES`
### Phase 6 — Isabelle/HOL Formal Verification (`master`) ✅ COMPLETE
Five `.thy` files in `proof/` alongside existing VM proofs:
- [x] `ACL_Pin_Monotone.thy` — pin bit is set-only; no operation clears it
once set
- [x] `ACL_Inherit_Clears_Pin.thy` — `ACL-INHERIT` always produces an entry
with `acl_pinned = 0` regardless of source entry state
- [x] `ACL_TTL_Bounded.thy` — TTL counter is bounded above by
`ACL-TTL-COMPUTE` output; cannot grow unboundedly
- [x] `ACL_Emergency_Bypass.thy` — when `emergency_console = 1` the
allow/deny decision is never consulted
- [x] `ACL_No_Escalation.thy` — a child VM cannot produce a pinned entry
with higher privilege than its inherited mode
### Phase 7 — LithosAnanke Parity (COMPLETE — independently verified)
There is no separate `lithosananke` branch to merge/port to (post-split, this repo's `master`
is the sole production line) — that framing is stale. Verified present directly in current
`master`: `acl_recheck()`/`zuse_session`/`emergency_console` wiring confirmed in
`src/starkernel/vm/vm_core.c`; the per-iteration `emergency_console = zuse_session ? 0 : 1`
assignment confirmed in `src/starkernel/repl.c`; the old `!vm->zuse_session` ACL-check bypass
confirmed absent from `src/vm.c`. See `.claude/CLAUDE.md`'s Word-Level ACL System section for
the full verification writeup.
- [x] Verify `ACL.4th` loads cleanly in kernel context (freestanding)
- [x] `ACL-BOOT` runs at kernel boot before first `BIRTH`
- [x] `vm->emergency_console` wired to kernel REPL active flag
- [x] `vm->zuse_session` wired to kernel Zuse console authentication path
- [x] Three-arch acceptance: amd64, aarch64, riscv64 boot to `ok>` with ACL
active and no regressions
- [x] Acceptance logs — this repo's standing convention commits every acceptance boot's serial
log under `logs/`, not a one-time Phase 7 action
### Phase 8 — PKI / Thumbdrive Authentication (FUTURE)
- [ ] Ed25519 challenge-response authentication for Zuse thumbdrive
- [ ] `tools/mkcapsule` embeds real Ed25519 CA key into `ACL.4th` Block 2066
at build time (replacing zero placeholder)
- [ ] User minting: admin creates thumbdrive with CA-signed certificate + home
block image
- [ ] Lose the drive → admin mints a new one; no software recovery path by
design
- [ ] `zuse.4th` becomes the bootstrapper that validates the physical drive
before granting Zuse session access
+24
View File
@@ -0,0 +1,24 @@
# docs/reference/word-acl/
Design doc for the word-level ACL (access control) system.
- **[DESIGN.md](DESIGN.md)** — Full design: three ACL dispositions per word
(`STRICT`, `TTL`, `PINNED`), the four `DictEntry` fields (`acl_ttl`,
`acl_allow`, `acl_mode`, `acl_pinned`), the two VM flags
(`emergency_console`, `zuse_session`), and the implementation split between
C infrastructure and the pure-FORTH policy capsule `capsules/ACL.4th`.
Status: implemented through Phase 6 on `master`; Phase 7 (LithosAnanke
parity) is next.
Implementation files referenced by this design: `capsules/ACL.4th`,
`capsules/zuse.4th`, `src/word_source/acl_words.c`,
`src/test_runner/modules/acl_words_test.c`, and the ACL proof set in
`proof/` (`ACL_Pin_Monotone.thy`, `ACL_Inherit_Clears_Pin.thy`,
`ACL_TTL_Bounded.thy`, `ACL_Emergency_Bypass.thy`, `ACL_No_Escalation.thy`).
## See also
- [`proof/README.md`](../../../proof/README.md) — the ACL proof theory files.
- [`capsules/README.md`](../../../capsules/README.md) — capsule files
including `ACL.4th` and `zuse.4th`.
- [Project root](../../../README.md)