47 KiB
The Mesh ROM Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: A small ROM for mesh nodes holding shared math (UM*, UM/MOD, D+, DNEGATE, Q.*, Q./), the same six routines as entries 0–5 of the host ROM, with the system's words of those names calling the ROM.
Architecture: One F18 source, capsules/v4/rom/math.f18, is assembled into both ROMs by v4/tools/mkrom.c. A C X-macro, v4/include/v4/rom_entries.h, is the one list of the six entries. v4/tools/mkmap.c turns it into the (MATH-ENTRIES) macro that both ROMs' entry tables start with, and into the ROM-UM* … constants the system's source uses. The system's six words become ROM-xxx push ;. In the test host, a node Hera births carries the mesh ROM.
Tech Stack: C99 (-Wall -Wextra -Wpedantic -Werror), the v4 text assembler notation (v4/include/v4/text.h), GNU make, QEMU for acceptance.
Spec: docs/v4.0.0/MACHINE.md section 2.2 (commit a6da45df), with sections 2 and 2.1 above it.
Global Constraints
- Work on branch
StarForth-v4.0.0in the main checkout. Never create a branch, never stash, never--no-verify. - Commit with
G="git -c filter.lfs.process= -c filter.lfs.clean=cat -c filter.lfs.smudge=cat -c filter.lfs.required=false"; push withPATH="$PATH:/usr/share/gitkraken/resources/app.asar.unpacked/git/libexec/git-core" $G push -q origin StarForth-v4.0.0. Commit messages endCo-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>. - Never apply a fix that is not in this plan; report it.
- Nothing language-specific in the ROM: no names, no headers, no FORTH (MACHINE.md 7.1).
- The entry order is fixed: 0
UM*, 1UM/MOD, 2D+, 3DNEGATE, 4Q.*, 5Q./; the host ROM's assembler routines are 6–17 and(ROM)is 18. - The six routines behave exactly as the system's words did before this plan (the tables in Task 1 are those results).
- Acceptance (
.claude/CLAUDE.md):make -f kernel/Makefile ARCH=<arch> clean qemufor amd64, aarch64 and riscv64, one QEMU at a time in the foreground, each took>withPARITY:V4_POST tests=538 pass=538 fail=0andPOST: PASSED,PARITY:V4_SYSTEM dict_hash=equal across the three; andmake -C v4 test-32with 0 failures. Logs are committed plain.git status --short diskmust be clean before boots. - The suite for every task:
make -C v4 test test-32 hosted-check.
Rulings made while planning
- The system's words call the ROM with
ROM-xxx push ;, notjump ROM-xxx. The spec says "a singlejump". The text assembler refuses a branch to a constant ("not something to branch to",v4/src/text.cbranch), and the ROM's assembler must accept the same language word for word.push ;puts the entry's address on the return stack and returns into it: the same effect, the same peak return-stack depth, one cell more. Cost if wrong: a language extension intext.cand in the ROM's assembler, and six one-line edits. host_load(v4/tests/host_map.h) attaches the host ROM, so everytest_host_*test links the ROM. Without it, a system thattext.cbuilds for a test faults at its firstUM*.mkmapincludeshost_map.hbut cannot link the ROM (the ROM is built from mkmap's output), so the attach is compiled only whereV4_ROM_LINKEDis defined. Cost if wrong: the define and one Makefile line.- The mesh ROM is checked to be under 256 words (the spec says about 150 to 200). Cost if wrong: one number in a test.
- The products do not link the mesh ROM. They have no births yet (spec, "Births"), so it would be dead code there.
make -C v4 rombuilds it at both widths. Cost if wrong: one object inkernel/Makefile.
Review Focus
- An error raised inside a ROM routine on a node with a prompt.
Q./by zero stores 11 into NODE-ERROR, which traps; the prompt must print "Division by zero" and come back, as it did when the code was in RAM. Pinned by ahosted-checkline in Task 2. - A node that runs the system without its ROM attached. It must fault at the first call into the ROM range, never compute a wrong answer silently. Pinned in Task 2 (a detached ROM makes
UM*not return). - 32-bit cells. Every table in Task 1 has its 32-bit values;
make -C v4 test-32runs them. - The two entry lists cannot drift. The F18 macro is generated from the C X-macro, and Task 1 checks that each entry word of both ROMs jumps to the routine of that name.
- Return-stack depth through the trampoline at the deepest Q48 chains (
Q.LOG→Q./→(UQ/)→D2*C). The trampoline'spushis popped by its;before the routine runs, so the peak is unchanged; POST's Q48 cases exercise it on the host.
Task 1: The math in both ROMs, through one entry list
Files:
- Create:
v4/include/v4/rom_entries.h - Create:
capsules/v4/rom/math.f18 - Create:
capsules/v4/rom/mesh.f18 - Create:
v4/tests/test_host_rom_math.c - Modify:
capsules/v4/rom/main.f18(the entry table) - Modify:
v4/tools/mkmap.c(the(MATH-ENTRIES)macro and the math's constants in the ROM map) - Modify:
v4/tools/mkrom.c(an array-name argument) - Modify:
v4/Makefile(the mesh ROM,math.f18in the host ROM) - Modify:
v4/tests/rom_harness.h(the entry list, the start, the mesh ROM's externs) - Modify:
v4/tests/test_host_rom_asm.c:72(the start) - Modify:
v4/system/boot.c:415-430(the start)
Interfaces:
-
Produces:
V4_ROM_MATH_ENTRIES(X)withX(index, routine, forth, effect);V4_ROM_MATH_COUNT(6);V4_ROM_START(18). -
Produces: arrays
v4_rom_<w>[]/v4_rom_<w>_words(unchanged names) andv4_mesh_rom_<w>[]/v4_mesh_rom_<w>_words; files$(BINDIR)/rom-<w>.{c,sym}and$(BINDIR)/mesh-rom-<w>.{c,sym}. -
Produces: in
rom_harness.h,MESH_ROM,MESH_ROM_WORDS; the test defineV4_MESH_ROM_SYM. -
Step 1: Write the failing test
Create v4/tests/test_host_rom_math.c:
/* test_host_rom_math.c -- the ROMs' shared math (docs/v4.0.0/MACHINE.md 2.2).
*
* Both ROMs begin with the same six entries (v4/include/v4/rom_entries.h):
* UM*, UM/MOD, D+, DNEGATE, Q.* and Q./. Each is called through its entry,
* on the host ROM and on the mesh ROM, with the values below, and must give
* what the system's words of those names gave before they moved into the
* ROM. The tables are those words' results as text.c assembled the system
* on 2026-10-15, before the move, at each width.
*/
#include "v4/rom_entries.h"
#include "v4/iword.h"
#include "rom_harness.h"
static int failures = 0, checks = 0;
#define CHECK(c,...) do{checks++; if(!(c)){failures++; printf("FAIL %s:%d: ",__FILE__,__LINE__); printf(__VA_ARGS__); printf("\n");}}while(0)
#define M ((v4_cell)V4_MSB) /* the most negative cell */
#define X ((v4_cell)~(v4_ucell)V4_MSB) /* the largest */
#define ONES (~(v4_ucell)0)
#define CANARY ((v4_cell)0x0C0FFEE5)
/* entry, arguments (deepest first), results (deepest first), NODE-ERROR after */
typedef struct { unsigned entry, argc; v4_cell in[4]; unsigned outc; v4_cell out[2]; v4_cell error; } math_case;
static const math_case cases[] = {
{ 0, 2, {0, 0}, 2, {0, 0}, 0 },
{ 0, 2, {1, -1}, 2, {-1, 0}, 0 },
{ 0, 2, {-1, -1}, 2, {1, -2}, 0 },
{ 0, 2, {M, 2}, 2, {0, 1}, 0 },
{ 0, 2, {12345, 67890}, 2, {838102050, 0}, 0 },
{ 0, 2, {-1, M}, 2, {M, X}, 0 },
{ 0, 2, {X, X}, 2, {1, X / 2}, 0 },
{ 1, 3, {0, 0, 1}, 2, {0, 0}, 0 },
{ 1, 3, {100, 0, 7}, 2, {2, 14}, 0 },
{ 1, 3, {-1, -2, -1}, 2, {-2, -1}, 0 },
{ 1, 3, {0, 1, 3}, 2, {1, (v4_cell)(ONES / 3u)}, 0 },
{ 1, 3, {-1, 0, 10}, 2, {5, (v4_cell)(ONES / 10u)}, 0 },
#if V4_CELL_BITS == 64
{ 1, 3, {12345, 6, 7}, 2, {2, -2635249153387077039LL}, 0 },
#else
{ 1, 3, {12345, 6, 7}, 2, {0, -613564993L}, 0 },
#endif
{ 1, 3, {M, X - 1, X}, 2, {X - 1, -2}, 0 },
{ 1, 3, {-1, X, M}, 2, {X, -1}, 0 },
{ 2, 4, {-1, 0, 1, 0}, 2, {0, 1}, 0 },
{ 2, 4, {-1, -1, 1, 0}, 2, {0, 0}, 0 },
{ 2, 4, {M, 0, M, 0}, 2, {0, 1}, 0 },
{ 2, 4, {5, -1, -6, 0}, 2, {-1, -1}, 0 },
{ 2, 4, {0, X, 0, 1}, 2, {0, M}, 0 },
{ 2, 4, {123, 4, -200, -5}, 2, {-77, -1}, 0 },
{ 3, 2, {0, 0}, 2, {0, 0}, 0 },
{ 3, 2, {1, 0}, 2, {-1, -1}, 0 },
{ 3, 2, {0, 1}, 2, {0, -1}, 0 },
{ 3, 2, {-1, -1}, 2, {1, 0}, 0 },
{ 3, 2, {0, M}, 2, {0, M}, 0 },
{ 3, 2, {5, 0}, 2, {-5, -1}, 0 },
{ 4, 4, {65536, 0, 65536, 0}, 2, {65536, 0}, 0 },
{ 4, 4, {163840, 0, -196608, -1}, 2, {-491520, -1}, 0 },
{ 4, 4, {-65536, -1, -65536, -1}, 2, {65536, 0}, 0 },
{ 4, 4, {32768, 0, 32768, 0}, 2, {16384, 0}, 0 },
{ 4, 4, {0, 0, 65536, 0}, 2, {0, 0}, 0 },
{ 4, 4, {0, 1, 0, 1}, 2, {0, (v4_cell)1 << (V4_CELL_BITS - 16)}, 0 },
{ 4, 4, {-1, X, 131072, 0}, 2, {-2, -1}, 0 },
{ 4, 4, {1, 0, 1, 0}, 2, {0, 0}, 0 },
{ 5, 4, {65536, 0, 196608, 0}, 2, {21845, 0}, 0 },
{ 5, 4, {-491520, -1, 131072, 0}, 2, {-245760, -1}, 0 },
{ 5, 4, {65536, 0, 0, 0}, 2, {-1, X}, 11 },
{ 5, 4, {-65536, -1, 0, 0}, 2, {0, M}, 11 },
{ 5, 4, {0, 0, 0, 0}, 2, {0, 0}, 11 },
{ 5, 4, {0, 1, 1, 0}, 2, {0, 65536}, 0 },
{ 5, 4, {-1, X, 32768, 0}, 2, {-1, X}, 0 },
{ 5, 4, {M, -1, -65536, -1}, 2, {M, 0}, 0 },
{ 5, 4, {1, 0, 65536, 0}, 2, {1, 0}, 0 },
};
#define CASES (sizeof cases / sizeof cases[0])
static v4_node n;
/* Call entry c->entry of `rom`. 1 if it returned with the canary under its
* results; got[] the results, deepest first, *err NODE-ERROR. */
static int run(const v4_cell *rom, unsigned words, const math_case *c, v4_cell *got, v4_cell *err)
{
unsigned i;
rom_boot(&n);
v4_node_rom_attach(&n, rom, words);
v4_dstack_push(&n.ds, CANARY);
for (i = 0; i < c->argc; i++) v4_dstack_push(&n.ds, c->in[i]);
if (v4_test_call(&n, &rom_es, &rom_h, V4_ROM_BASE + (v4_cell)c->entry, 10000000L) <= 0) return 0;
for (i = c->outc; i-- > 0; ) got[i] = v4_dstack_pop(&n.ds);
*err = n.mem[NODE_ERROR];
return v4_dstack_pop(&n.ds) == CANARY;
}
static void cases_on(const char *which, const v4_cell *rom, unsigned words)
{
unsigned k;
for (k = 0; k < CASES; k++) {
const math_case *c = &cases[k];
v4_cell got[2] = { 0, 0 }, err = -99;
int ok = run(rom, words, c, got, &err);
CHECK(ok && got[0] == c->out[0] && got[1] == c->out[1] && err == c->error,
"%s ROM, entry %u, case %u: got %lld %lld error %lld, want %lld %lld error %lld",
which, c->entry, k, (long long)got[0], (long long)got[1], (long long)err,
(long long)c->out[0], (long long)c->out[1], (long long)c->error);
}
}
/* The address of `name` in a ROM's symbol file, or -1. */
static long sym_addr(const char *path, const char *name)
{
FILE *f = fopen(path, "r");
char sym[80];
long addr, found = -1;
if (!f) { printf(" cannot read %s\n", path); return -1; }
while (fscanf(f, "%79s %ld", sym, &addr) == 2) if (strcmp(sym, name) == 0) found = addr;
fclose(f);
return found;
}
/* Entry i of `rom` is a jump, in slot 0, to the routine `routine`. */
static int entry_jumps_to(const v4_cell *rom, unsigned i, const char *sym, const char *routine)
{
v4_iword w = (v4_iword)((v4_ucell)rom[i] & 0xFFFFFFFFu);
long target = sym_addr(sym, routine);
return target >= 0 && v4_iword_op(w, 0) == V4_OP_JUMP
&& v4_iword_branch(V4_ROM_BASE + (v4_cell)i, w, 0) == (v4_cell)target;
}
int main(void)
{
printf("v4 ROM math tests: V4_CELL_BITS=%d\n", V4_CELL_BITS);
/* ---- one entry list, in both ROMs ---- */
{
#define CHECK_ENTRY(i, routine, forth, effect) \
CHECK(entry_jumps_to(ROM_IMAGE, i, V4_ROM_SYM, routine), "host ROM entry %d jumps to %s", i, routine); \
CHECK(entry_jumps_to(MESH_ROM, i, V4_MESH_ROM_SYM, routine), "mesh ROM entry %d jumps to %s", i, routine);
V4_ROM_MATH_ENTRIES(CHECK_ENTRY)
#undef CHECK_ENTRY
}
CHECK(entry_jumps_to(ROM_IMAGE, V4_ROM_START, V4_ROM_SYM, "(ROM)"), "the host ROM starts at entry %u, (ROM)", (unsigned)V4_ROM_START);
CHECK(entry_jumps_to(ROM_IMAGE, V4_ROM_MATH_COUNT, V4_ROM_SYM, "(KEY)"), "the assembler's entries follow the math's");
/* ---- the mesh ROM is small and holds nothing of the assembler ---- */
CHECK(MESH_ROM_WORDS < 256u, "the mesh ROM is small: %u words", MESH_ROM_WORDS);
CHECK(sym_addr(V4_MESH_ROM_SYM, "(TOKEN)") < 0 && sym_addr(V4_MESH_ROM_SYM, "(KEY)") < 0 && sym_addr(V4_MESH_ROM_SYM, "(ROM)") < 0,
"the mesh ROM has none of the assembler's routines");
printf(" the host ROM: %u words; the mesh ROM: %u words\n", ROM_IMAGE_WORDS, MESH_ROM_WORDS);
/* ---- each entry, on each ROM, gives what the system's words gave ---- */
cases_on("host", ROM_IMAGE, ROM_IMAGE_WORDS);
cases_on("mesh", MESH_ROM, MESH_ROM_WORDS);
printf(" %d checks, %d failures\n", checks, failures);
return failures != 0;
}
- Step 2: Run it to verify it fails
Run: make -C v4 build/64-test_host_rom_math.c 2>&1 | tail -5
Expected: FAIL to build: v4/rom_entries.h: No such file or directory.
- Step 3: The one entry list
Create v4/include/v4/rom_entries.h:
/* rom_entries.h -- the ROMs' shared entries (docs/v4.0.0/MACHINE.md 2.2).
*
* Both ROMs, the host node's and a mesh node's, begin with these entries, in
* this order: entry i is at V4_ROM_BASE + i, a jump to the routine. A
* payload calls an entry, never an address inside the ROM. This is the one
* list: tools/mkmap.c makes of it the (MATH-ENTRIES) macro both ROMs' entry
* tables begin with (capsules/v4/rom/main.f18, mesh.f18) and the constants
* ROM-UM* ... the system's source calls them by (tests/host_map.h).
*
* X(INDEX, ROUTINE, FORTH, EFFECT): ROUTINE is the ROM's name for the code
* (capsules/v4/rom/math.f18), FORTH the system's word that calls it, EFFECT
* its stack effect. An entry is added only at the end.
*/
#ifndef V4_ROM_ENTRIES_H
#define V4_ROM_ENTRIES_H
#define V4_ROM_MATH_ENTRIES(X) \
X(0, "(UM*)", "UM*", "( u1 u2 -- ulo uhi )") \
X(1, "(UM/MOD)", "UM/MOD", "( ulo uhi ud -- urem uquot )") \
X(2, "(D+)", "D+", "( d1 d2 -- d3 )") \
X(3, "(DNEGATE)", "DNEGATE", "( d -- -d )") \
X(4, "(Q.*)", "Q.*", "( a b -- c )") \
X(5, "(Q./)", "Q./", "( a b -- q )")
#define V4_ROM_MATH_COUNT 6u
/* The host ROM's own entries follow: the assembler's twelve routines, then
* (ROM), where the host machine starts (capsules/v4/rom/main.f18). */
#define V4_ROM_START (V4_ROM_MATH_COUNT + 12u)
#endif
- Step 4: The math, in ROM source
Create capsules/v4/rom/math.f18. The routine bodies are the system's, copied without change from capsules/v4/sys/core.f18 (UM*, D+, DNEGATE), numout.f18 (UM/MOD) and qmath.f18 (Q.TO-INT, Q.*, D2*C, (UQ/), Q./, (QERR11)), with the names the ROM gives them and calls between them renamed to match:
\ math.f18 -- the ROM's shared math (docs/v4.0.0/MACHINE.md 2.2).
\
\ Six routines a payload calls through the ROM's entry table, as a GA144
\ application calls its node ROM's multiply and divide steps, and their
\ helpers. Both ROMs carry them: the host node's and a mesh node's. They
\ were the system's (capsules/v4/sys: core.f18, numout.f18, qmath.f18) and
\ do exactly what they did there; the system's words of those names now call
\ these. No names, no headers, no FORTH.
\
\ Constants from the map (tools/mkmap.c): N-1 NODE-ERROR MSB MAXHI 2N+15
\ N-18 N-17, and (Q/), Q./'s five scratch cells, fixed in the memory map
\ (MACHINE.md 2.1).
\
\ ERRORS. Q./ by zero leaves the result D-11 gives and stores 11, "Division
\ by zero", into NODE-ERROR (D-18).
macro SWAP over push push drop pop pop endmacro
macro - push inv pop + inv endmacro \ x y -- x-y
macro NIP push drop pop endmacro
macro ROT push SWAP pop SWAP endmacro
macro OR over inv and xor endmacro
\ ( u1 u2 -- ulo uhi ) entry 0
: (UM*)
over 1 and inv 1 + over 2/ and \ u1 u2 t0 t0 = u2 2/ if u1 odd
over a! N-1 push \ A: u2 R: loop count
push over 2/ pop \ u1 u2 s t0 s = u1 2/
L: +* unext \ u1 u2 s hi A: lo
push drop pop \ u1 u2 hi
2* a -if L0 drop 1 + jump L1 L0: drop L1: push
over -if L2 drop dup jump L3 L2: drop 0 L3: push
dup -if L4 drop over 1 and jump L5 L4: drop 0 L5:
pop + pop + push
and 1 and a 2* + pop ;
\ ( ulo uhi ud -- urem uquot ) entry 1. One step a bit, the divisor in
\ A. Exact for uhi < ud.
: (UM/MOD)
a! N-1 FOR
-if L0
2* over -if L1 drop 1 + jump L2
L1: drop L2: push 2* pop
jump SUB
L0:
2* over -if L3 drop 1 + jump L4
L3: drop L4: push 2* pop
dup a xor -if L5
drop -if NOSUB jump SUB
L5: drop dup inv a + inv -if L6
drop jump NOSUB
L6: push drop pop jump SETBIT
SUB: inv a + inv
SETBIT: push 1 + pop
NOSUB:
NEXT
over push push drop pop pop ;
\ ( d1 d2 -- d3 ) entry 2
: (D+)
push over push push drop pop \ al bl R: bh ah
over over xor -if L1
drop + -if C1 jump C0
L1: drop over -if L2
drop + jump C1
L2: drop +
C0: pop pop + ;
C1: pop pop + 1 + ;
\ ( d -- -d ) entry 3
: (DNEGATE)
inv over if L1 drop push inv 1 + pop ;
L1: drop 1 + ;
\ ( q -- n ) the system's Q.TO-INT, which Q.* needs
: (Q>INT) push a! 0 pop 15 FOR +* UNEXT drop drop a ;
\ ( a b -- c ) entry 4
: (Q.*)
SWAP push over over (UM*) drop
push push over pop
-if L1 SWAP jump L2 L1: SWAP drop 0 L2:
pop SWAP - push
push over pop (UM*) pop +
ROT pop dup push
over -if L3 drop dup jump L4 L3: drop 0 L4:
push (UM*) pop - (D+)
ROT pop (UM*)
SWAP push 0 (D+) pop
push over pop SWAP (Q>INT) push (Q>INT) pop SWAP ;
: (QERR11) NODE-ERROR b! 11 !b ;
: (D2*C) ( lo hi cin -- lo' hi' cout )
a! dup -if L1 drop 1 jump L2 L1: drop 0 L2: push
over -if L3 drop 1 jump L4 L3: drop 0 L4:
over + +
push 2* a + pop pop ;
Then append (UQ/) and (Q./): copy qmath.f18's : (UQ/) through the end of : Q./ (the line Z0: drop jump (QERR11)) verbatim, with exactly these renames and nothing else changed:
: Q./ ( a b -- q )becomes: (Q./) ( a b -- q ) \ entry 5;- every call
D2*Cbecomes(D2*C); - every call
DNEGATEbecomes(DNEGATE); - the call
(UQ/)inDIV:stays(UQ/), and: (UQ/)stays its name.
The set HDR-NFA … set HDR-LINK here header line before : Q./ is not copied: the ROM has no headers.
- Step 5: The entry tables
Create capsules/v4/rom/mesh.f18:
\ mesh.f18 -- a mesh node's ROM: its entry table (docs/v4.0.0/MACHINE.md 2.2).
\
\ A mesh node's ROM is the shared math (math.f18) and nothing else. It has
\ no start: a mesh node at reset blocks on its ports and executes what
\ arrives (docs/v4.0.0/MESH.md 4.2 and 5). Its entries are the first of
\ the host ROM's, at the same numbers: (MATH-ENTRIES), which tools/mkmap.c
\ writes from v4/include/v4/rom_entries.h.
: (ENTRIES)
(MATH-ENTRIES)
In capsules/v4/rom/main.f18, replace the header comment's last sentence and the table. The table becomes:
: (ENTRIES)
(MATH-ENTRIES) \ 0-5 the shared math (math.f18; v4/rom_entries.h)
jump (KEY) \ 6 ( -- c )
jump (TOKEN) \ 7 ( -- len )
jump (NUMBER) \ 8 ( -- n flag )
jump (FIND) \ 9 ( -- rec | 0 )
jump (DEFINE) \ 10 ( value kind -- )
jump (OPCODE) \ 11 ( -- op | -1 )
jump (OP) \ 12 ( op -- )
jump (LIT) \ 13 ( v -- )
jump (BRANCH) \ 14 ( op target -- )
jump (BRANCH>) \ 15 ( op -- ref )
jump (RESOLVE) \ 16 ( ref target -- )
jump (LABEL) \ 17 ( -- addr )
jump (ROM) \ 18 the ROM itself: where the machine starts (V4_ROM_START)
and the header comment's "DATASHEET-F18.md lists each routine's stack effect." becomes "Entries 0-5 are the shared math, the same in a mesh node's ROM (mesh.f18, MACHINE.md 2.2). DATASHEET-F18.md lists each routine's stack effect."
- Step 6: The ROM map gives the macro and the math's constants
In v4/tools/mkmap.c, add #include "../include/v4/rom_entries.h" after the host_map.h include, and in main's default branch, after the HEX-HIGH constant and before return 0;:
/* the math's constants (capsules/v4/rom/math.f18) */
constant("N-1", (long)(V4_CELL_BITS - 1));
constant("NODE-ERROR", (long)NODE_ERROR);
constant("(Q/)", (long)QBASE);
constant("MAXHI", (long)(v4_cell)(V4_MSB - 1u));
constant("2N+15", (long)(2 * V4_CELL_BITS + 15));
constant("N-18", (long)(V4_CELL_BITS - 18));
constant("N-17", (long)(V4_CELL_BITS - 17));
/* the shared entries, the start of both ROMs' tables (v4/rom_entries.h) */
printf("macro (MATH-ENTRIES)");
#define MATH_ENTRY_JUMP(i, routine, forth, effect) printf(" jump %s", routine);
V4_ROM_MATH_ENTRIES(MATH_ENTRY_JUMP)
#undef MATH_ENTRY_JUMP
printf(" endmacro\n");
Also update the file's header comment: "the console's receive registers and the ROM's state area (MACHINE.md 2.1)" becomes "the console's receive registers, the ROM's state area (MACHINE.md 2.1), the math's constants and its shared entries (MACHINE.md 2.2)".
- Step 7: mkrom names the array it writes
In v4/tools/mkrom.c: the usage becomes mkrom NAME OUT.c OUT.sym MAP.f18 SOURCE.f18..., writing const v4_cell v4_<NAME>_<width>[] and v4_<NAME>_<width>_words. Concretely:
-
header comment line
mkrom OUT.c OUT.sym MAP.f18 SOURCE.f18...→mkrom NAME OUT.c OUT.sym MAP.f18 SOURCE.f18..., andOUT.c the ROM: const v4_cell v4_rom_<width>[] and its length→OUT.c the ROM: const v4_cell v4_<NAME>_<width>[] and its length (NAME rom: the host node's ROM; mesh_rom: a mesh node's, MACHINE.md 2.2); -
if (argc < 5)→if (argc < 6), and its usage text to match; -
every
argv[1]→argv[2], everyargv[2](the symbol file) →argv[3], and the loopfor (i = 3; ...)→for (i = 4; ...); -
the two
fprintflines that name the array:"const v4_cell v4_rom_%d[] = {\n", V4_CELL_BITS→"const v4_cell v4_%s_%d[] = {\n", argv[1], V4_CELL_BITS, and"};\nconst unsigned v4_rom_%d_words = %ldu;\n", V4_CELL_BITS, ...→"};\nconst unsigned v4_%s_%d_words = %ldu;\n", argv[1], V4_CELL_BITS, ...; -
the final message
"mkrom: a ROM of %ld words, ..."gains the name:"mkrom: %s, %ld words, %d-bit cells -> %s\n", argv[1], ...withargv[2]for the output file. -
Step 8: The Makefile builds both ROMs
In v4/Makefile:
ROM_FILESgainsmath.f18afterdir.f18:$(wildcard $(addprefix $(ROM_DIR)/,main.f18 in.f18 num.f18 sym.f18 pack.f18 word.f18 dir.f18 math.f18)).- After it:
MESH_ROM_FILES := $(wildcard $(addprefix $(ROM_DIR)/,mesh.f18 math.f18))with the comment# A mesh node's ROM (MACHINE.md 2.2): its entry table, then the shared math. rom_src = $(if $(call uses_rom,$(1)),$(BINDIR)/rom-$(2).c $(BINDIR)/mesh-rom-$(2).c)rom_sym = $(if $(call uses_rom,$(1)),-DV4_ROM_SYM='"$(BINDIR)/rom-$(2).sym"' -DV4_MESH_ROM_SYM='"$(BINDIR)/mesh-rom-$(2).sym"')- In
ROM_RULE, themkmapandmkromprerequisites gain$(HERE)/include/v4/rom_entries.honly through the existing$$(wildcard $(HERE)/include/v4/*.h)(already there; nothing to add). The host ROM's recipe becomes$(BINDIR)/mkrom-$(1) rom $(BINDIR)/rom-$(1).c ..., and a new rule follows it:
$(BINDIR)/mesh-rom-$(1).c $(BINDIR)/mesh-rom-$(1).sym: $(BINDIR)/mkrom-$(1) $(BINDIR)/rom-map-$(1).f18 $$(MESH_ROM_FILES)
$(BINDIR)/mkrom-$(1) mesh_rom $(BINDIR)/mesh-rom-$(1).c $(BINDIR)/mesh-rom-$(1).sym $(BINDIR)/rom-map-$(1).f18 $$(MESH_ROM_FILES)
-
rom:becomes$(foreach w,$(WIDTHS),$(BINDIR)/rom-$(w).c $(BINDIR)/mesh-rom-$(w).c). -
The ROM comment block gains:
math.f18, the shared math, is in both: tools/mkrom.c builds mesh-rom-<w>.c from mesh.f18 and math.f18 (MACHINE.md 2.2). -
Step 9: The start moves to entry 18, and the harness knows the mesh ROM
In v4/tests/rom_harness.h:
- add
#include "v4/rom_entries.h"after#include "v4/sys_files.h"; - after the
ROM_IMAGEblock, add:
/* A mesh node's ROM (MACHINE.md 2.2): the shared math only. */
#if V4_CELL_BITS == 64
extern const v4_cell v4_mesh_rom_64[];
extern const unsigned v4_mesh_rom_64_words;
#define MESH_ROM v4_mesh_rom_64
#define MESH_ROM_WORDS v4_mesh_rom_64_words
#else
extern const v4_cell v4_mesh_rom_32[];
extern const unsigned v4_mesh_rom_32_words;
#define MESH_ROM v4_mesh_rom_32
#define MESH_ROM_WORDS v4_mesh_rom_32_words
#endif
rom_entries[]becomes:
#define ROM_MATH_NAME(i, routine, forth, effect) routine,
static const char *const rom_entries[] = {
V4_ROM_MATH_ENTRIES(ROM_MATH_NAME)
"(KEY)", "(TOKEN)", "(NUMBER)", "(FIND)", "(DEFINE)", "(OPCODE)",
"(OP)", "(LIT)", "(BRANCH)", "(BRANCH>)", "(RESOLVE)", "(LABEL)", "(ROM)"
};
#undef ROM_MATH_NAME
- in
rom_build_system,n->p = V4_ROM_BASE + 12; /* entry 12, (ROM): ... */→n->p = V4_ROM_BASE + (v4_cell)V4_ROM_START; /* (ROM): where the machine starts */.
In v4/tests/test_host_rom_asm.c:72, b.p = V4_ROM_BASE + 12; → b.p = V4_ROM_BASE + (v4_cell)V4_ROM_START; (it includes rom_harness.h, which now includes rom_entries.h).
In v4/system/boot.c: add #include "v4/rom_entries.h" with the other v4/ includes; delete #define ROM_START 12u /* the entry where the machine starts */; n->p = V4_ROM_BASE + (v4_cell)ROM_START; → n->p = V4_ROM_BASE + (v4_cell)V4_ROM_START;; and the comment "The machine starts at its entry 12, (ROM)" → "The machine starts at its entry V4_ROM_START (v4/rom_entries.h), (ROM)".
- Step 10: Run the test to verify it passes
Run: make -C v4 build/64-test_host_rom_math.c build/32-test_host_rom_math.c && v4/build/64-test_host_rom_math.c && v4/build/32-test_host_rom_math.c
Expected: both print the host ROM: N words; the mesh ROM: M words with M under 256, and ... checks, 0 failures. (If the Makefile's test binary names differ from build/<w>-test_host_rom_math.c, use the names make -C v4 test builds; rule and continue.)
- Step 11: Run the suite
Run: make -C v4 test test-32 hosted-check > /tmp/claude-1000/-home-rajames-CLionProjects-LithosAnanake/7f40fa84-d337-4c0c-bdd7-61dc32496f02/scratchpad/suite.txt 2>&1; echo EXIT $?; grep -E 'FAIL|failures|hosted-check' /tmp/claude-1000/-home-rajames-CLionProjects-LithosAnanake/7f40fa84-d337-4c0c-bdd7-61dc32496f02/scratchpad/suite.txt | grep -v ' 0 failures' | head
Expected: EXIT 0, no failures. The system still has its own math in RAM, so dict_hash is unchanged; the ROM's hash changes.
- Step 12: Commit
$G add v4/include/v4/rom_entries.h capsules/v4/rom/math.f18 capsules/v4/rom/mesh.f18 capsules/v4/rom/main.f18 \
v4/tools/mkmap.c v4/tools/mkrom.c v4/Makefile v4/tests/rom_harness.h v4/tests/test_host_rom_asm.c \
v4/tests/test_host_rom_math.c v4/system/boot.c
$G commit -m "feat(v4): the shared math in both ROMs, and a mesh node's ROM
MACHINE.md 2.2. UM*, UM/MOD, D+, DNEGATE, Q.* and Q./ are entries 0-5
of the host ROM and of a mesh node's ROM, from one source (rom/math.f18)
and one entry list (v4/rom_entries.h). The host ROM starts at entry 18.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
Task 2: The system calls the ROM
Files:
- Modify:
v4/tests/host_map.h(ROM-entry constants;host_loadattaches the host ROM) - Modify:
v4/tools/mkmap.c(mkmap syswrites the ROM-entry constants) - Modify:
v4/Makefile(everytest_host_*links the ROM and definesV4_ROM_LINKED; ahosted-checkline) - Modify:
v4/tests/rom_harness.h(the ROM externs move tohost_map.h) - Modify:
capsules/v4/sys/core.f18(UM*,D+,DNEGATE),numout.f18(UM/MOD),qmath.f18(Q.*,Q./;D2*C,(UQ/),(QERR11)removed) - Modify (generated):
capsules/v4/sys/map-64.f18,map-32.f18(make -C v4 sysmap) - Modify:
v4/tests/test_host_unit.c(host_bornattaches the host ROM) - Test:
v4/tests/test_host_rom_math.c
Interfaces:
-
Consumes:
V4_ROM_MATH_ENTRIES,MESH_ROM,ROM_IMAGEfrom Task 1. -
Produces: constants
ROM-UM*,ROM-UM/MOD,ROM-D+,ROM-DNEGATE,ROM-Q.*,ROM-Q./(=V4_ROM_BASE + index) in the system's map and inhost_load;host_loadleaves the host ROM attached. -
Step 1: Write the failing test
Append to v4/tests/test_host_rom_math.c, before int main:
/* THE SYSTEM CALLS THE ROM. The system as text.c builds it (host_load, the
* ROM attached): each of the six words is a literal of its entry's address,
* then push, then ; -- it goes to the entry and the entry's ; returns to the
* word's caller -- and it gives the table's results. On a node with no ROM
* the call faults rather than computing anything. */
static v4_text tx;
static int system_cases(void)
{
static const char *const files[] = {
#define SYS_FILE_NAME(file) #file ".f18",
V4_SYS_FILES(SYS_FILE_NAME)
#undef SYS_FILE_NAME
};
unsigned k;
if (!host_load(&tx, &n, files, (unsigned)(sizeof files / sizeof files[0])) || !v4_text_finish(&tx)) {
printf(" the system does not assemble: %s\n", v4_text_error(&tx));
return 0;
}
#define CHECK_CALLS(i, routine, forth, effect) { \
v4_cell w = v4_text_word(&tx, forth); \
v4_iword iw = w >= 0 ? (v4_iword)((v4_ucell)n.mem[w] & 0xFFFFFFFFu) : 0; \
CHECK(w >= 0 && v4_iword_op(iw, 0) == V4_OP_FETCH_P && v4_iword_op(iw, 1) == V4_OP_PUSH && v4_iword_op(iw, 2) == V4_OP_SEMI \
&& n.mem[w + 1] == V4_ROM_BASE + (v4_cell)(i), "the system's %s is the ROM's entry %d, then push and ;", forth, i); }
V4_ROM_MATH_ENTRIES(CHECK_CALLS)
#undef CHECK_CALLS
for (k = 0; k < CASES; k++) {
static const char *const forth_of[] = {
#define FORTH_NAME(i, routine, forth, effect) forth,
V4_ROM_MATH_ENTRIES(FORTH_NAME)
#undef FORTH_NAME
};
const math_case *c = &cases[k];
v4_cell got[2] = { 0, 0 };
unsigned i;
int ok;
v4_dstack_reset(&n.ds); v4_rstack_reset(&n.rs); v4_exec_reset(&rom_es); v4_heat_reset(&rom_h);
n.mem[NODE_ERROR] = 0;
v4_dstack_push(&n.ds, CANARY);
for (i = 0; i < c->argc; i++) v4_dstack_push(&n.ds, c->in[i]);
ok = v4_test_call(&n, &rom_es, &rom_h, v4_text_word(&tx, forth_of[c->entry]), 10000000L) > 0;
for (i = c->outc; i-- > 0; ) got[i] = v4_dstack_pop(&n.ds);
CHECK(ok && v4_dstack_pop(&n.ds) == CANARY && got[0] == c->out[0] && got[1] == c->out[1] && n.mem[NODE_ERROR] == c->error,
"the system's %s, case %u: got %lld %lld error %lld", forth_of[c->entry], k,
(long long)got[0], (long long)got[1], (long long)n.mem[NODE_ERROR]);
}
/* the same system with its ROM taken away: UM* does not return */
v4_node_rom_attach(&n, ROM_IMAGE, 0u);
v4_dstack_reset(&n.ds); v4_rstack_reset(&n.rs); v4_exec_reset(&rom_es); v4_heat_reset(&rom_h);
v4_dstack_push(&n.ds, 12345); v4_dstack_push(&n.ds, 67890);
CHECK(v4_test_call(&n, &rom_es, &rom_h, v4_text_word(&tx, "UM*"), 100000L) <= 0 || n.fault_kind == V4_FAULT_ADDRESS,
"with no ROM, the system's UM* faults at the ROM's address and computes nothing");
return 1;
}
and in main, before the final printf: CHECK(system_cases(), "the system the tests build with text.c");
v4_node_rom_attach(&n, ROM_IMAGE, 0u) attaches a ROM of no words, which leaves the range empty (node.c, v4_node_rom_attach).
- Step 2: Run it to verify it fails
Run: make -C v4 build/64-test_host_rom_math.c && v4/build/64-test_host_rom_math.c | grep -c FAIL
Expected: FAIL. Six the system's ... is the ROM's entry failures (the words are still the RAM code), and with no ROM, ... UM* faults fails (it computes the product). If instead the build fails because host_load does not know ROM-UM*, that is not yet reached: the system text does not use it yet, so the build succeeds and the checks fail as described.
- Step 3: The map gives the entries' constants, and host_load attaches the ROM
In v4/tests/host_map.h:
- add
#include "v4/rom_entries.h"with its other includes; - after
HOST_CONSTANTS, add:
/* The shared ROM entries (MACHINE.md 2.2): ROM-UM* ... , each the address
* of its entry. The system's six words of those names call these. */
#define HOST_ROM_ENTRY_CONSTANTS(X) \
X("ROM-UM*", V4_ROM_BASE + 0) X("ROM-UM/MOD", V4_ROM_BASE + 1) X("ROM-D+", V4_ROM_BASE + 2) \
X("ROM-DNEGATE", V4_ROM_BASE + 3) X("ROM-Q.*", V4_ROM_BASE + 4) X("ROM-Q./", V4_ROM_BASE + 5)
and directly under it, a compile-time check that this list matches rom_entries.h:
#define HOST_ROM_ENTRY_INDEX_OK(i, routine, forth, effect) typedef char host_rom_entry_##i[(i) < 6 ? 1 : -1];
V4_ROM_MATH_ENTRIES(HOST_ROM_ENTRY_INDEX_OK)
#undef HOST_ROM_ENTRY_INDEX_OK
typedef char host_rom_entry_count[V4_ROM_MATH_COUNT == 6u ? 1 : -1];
- move the
#if V4_CELL_BITS == 64 extern const v4_cell v4_rom_64[] ... #define ROM_IMAGE_WORDS ... #endifblock fromrom_harness.hintohost_map.h, unchanged, abovehost_load; - in
host_load, afterHOST_CONSTANTS(HOST_CONSTANT), addHOST_ROM_ENTRY_CONSTANTS(HOST_CONSTANT); and afterv4_node_stack_regs_attach(n, DSTACK_REG, RSTACK_REG);add:
#ifdef V4_ROM_LINKED
v4_node_rom_attach(n, ROM_IMAGE, ROM_IMAGE_WORDS); /* the system calls its math in the ROM (MACHINE.md 2.2) */
#endif
In v4/tools/mkmap.c's system_map, after HOST_CONSTANTS(SYS_CONSTANT), add HOST_ROM_ENTRY_CONSTANTS(SYS_CONSTANT).
In v4/Makefile: uses_rom = $(findstring /test_host_,$(1)) (every host test), and rom_sym gains -DV4_ROM_LINKED: rom_sym = $(if $(call uses_rom,$(1)),-DV4_ROM_LINKED -DV4_ROM_SYM='"$(BINDIR)/rom-$(2).sym"' -DV4_MESH_ROM_SYM='"$(BINDIR)/mesh-rom-$(2).sym"'). Update the comment above uses_rom: "Every test named test_host_*.c links the ROMs of its width: the system calls its math in the ROM (MACHINE.md 2.2)".
rom_harness.h keeps #include "host_map.h" and now gets ROM_IMAGE from it.
- Step 4: The system's six words call the ROM
In capsules/v4/sys/core.f18, keep each set HDR-NFA here ... header line exactly, and replace the definitions:
: UM* ( u1 u2 -- ulo uhi )and its body (throughand 1 and a 2* + pop ;) →: UM* ROM-UM* push ; \ ( u1 u2 -- ulo uhi ) the ROM's entry 0 (MACHINE.md 2.2): D+ ( d1 d2 -- d3 )and its body (throughC1: pop pop + 1 + ;) →: D+ ROM-D+ push ; \ ( d1 d2 -- d3 ) the ROM's entry 2: DNEGATE ( d -- -d )and its body (throughL1: drop 1 + ;) →: DNEGATE ROM-DNEGATE push ; \ ( d -- -d ) the ROM's entry 3
In capsules/v4/sys/numout.f18: the comment \ One step a bit, the divisor in A. Exact for uhi < ud. and : UM/MOD ... through its over push push drop pop pop ; →
\ Exact for uhi < ud. The ROM's entry 1 (MACHINE.md 2.2).
: UM/MOD ROM-UM/MOD push ; \ ( ulo uhi ud -- urem uquot )
In capsules/v4/sys/qmath.f18:
: Q.* ( a b -- c )throughpush over pop SWAP Q.TO-INT push Q.TO-INT pop SWAP ;→: Q.* ROM-Q.* push ; \ ( a b -- c ) the ROM's entry 4 (MACHINE.md 2.2)- delete
: D2*C ...and: (UQ/) ...whole (they are the ROM's now,capsules/v4/rom/math.f18); : Q./ ( a b -- q )throughZ0: drop jump (QERR11)→: Q./ ROM-Q./ push ; \ ( a b -- q ) the ROM's entry 5;- delete
: (QERR11) NODE-ERROR b! 11 !b ;(onlyQ./used it;(QERR12)stays); - in the header comment, "Constants the loader supplies" gains the line
\ ROM-Q.* ROM-Q./ the ROM's entries (MACHINE.md 2.2), and the(Q/)line becomes\ (Q/) (QE) (QR) (QL) (QT) (QP) word addresses of the words' scratch cells: 5, 8, 5, 7, 6 and 4 of them; (Q/) is the ROM's Q./'s.
If any other system word called D2*C, (UQ/) or (QERR11), the build fails with "never defined"; grep -n 'D2\*C\|(UQ/)\|(QERR11)' capsules/v4/sys/*.f18 must show none after the edit.
- Step 5: Regenerate the system's map, and have the test host's nodes carry the ROM
Run: make -C v4 sysmap
Expected: capsules/v4/sys/map-64.f18 and map-32.f18 gain six constant ROM-... lines (ROM-UM* is 16384 at both widths).
In v4/tests/test_host_unit.c's host_born, after v4_node_fault_attach(&pool[k].n, w_fault);, add:
v4_node_rom_attach(&pool[k].n, ROM_IMAGE, ROM_IMAGE_WORDS); /* the nucleus calls its math in the ROM (MACHINE.md 2.2) */
- Step 6: hosted-check: an error inside the ROM returns to the prompt
In v4/Makefile's hosted-check recipe, after the noone lines, add the same shape for a division by zero in the ROM (use the variable and file-naming of the neighbouring lines, $(BINDIR)/qdiv-amd64.txt):
@printf '65536 0 0 0 Q./\n3 .\n' | $(BINDIR)/starforth4-amd64 > $(BINDIR)/qdiv-amd64.txt 2>&1; true
@grep -q '^ok> Division by zero$$' $(BINDIR)/qdiv-amd64.txt && grep -q '^ok> 3 ok$$' $(BINDIR)/qdiv-amd64.txt || { echo "hosted-check: a division by zero inside the ROM's Q./ was not an error the node came back from"; tail -4 $(BINDIR)/qdiv-amd64.txt; exit 1; }
If the error is printed in another form, take the form the noone line's error shows (^ok> <message>$) and rule.
- Step 7: Run the test to verify it passes
Run: make -C v4 build/64-test_host_rom_math.c build/32-test_host_rom_math.c && v4/build/64-test_host_rom_math.c && v4/build/32-test_host_rom_math.c
Expected: 0 failures at both widths.
- Step 8: Run the suite
Run: make -C v4 test test-32 hosted-check > /tmp/claude-1000/-home-rajames-CLionProjects-LithosAnanake/7f40fa84-d337-4c0c-bdd7-61dc32496f02/scratchpad/suite.txt 2>&1; echo EXIT $?; grep -E 'FAIL|failures|hosted-check|dict_hash' /tmp/claude-1000/-home-rajames-CLionProjects-LithosAnanake/7f40fa84-d337-4c0c-bdd7-61dc32496f02/scratchpad/suite.txt | grep -v ' 0 failures' | head -20
Expected: EXIT 0; test_host_unit passes POST 538/538 on Hera; hosted-check reports the three ISAs boot identically. dict_hash differs from 0xe5a57f3eb72ff94a, the same on all three ISAs. Note the new value for the final report.
- Step 9: Commit
$G add v4/tests/host_map.h v4/tools/mkmap.c v4/Makefile v4/tests/rom_harness.h v4/tests/test_host_unit.c \
v4/tests/test_host_rom_math.c capsules/v4/sys/core.f18 capsules/v4/sys/numout.f18 capsules/v4/sys/qmath.f18 \
capsules/v4/sys/map-64.f18 capsules/v4/sys/map-32.f18
$G commit -m "feat(v4): the system's UM*, UM/MOD, D+, DNEGATE, Q.* and Q./ call the ROM
MACHINE.md 2.2. Each is its entry's address, push and ;. The tests'
systems carry the host ROM, and a division by zero inside the ROM
returns to the prompt (hosted-check).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
Task 3: A node Hera births carries the mesh ROM
Files:
- Modify:
v4/tests/test_host_unit.c(host_born; checks after the unit is born)
Interfaces:
-
Consumes:
MESH_ROM,MESH_ROM_WORDS,ROM_IMAGE(rom_harness.h,host_map.h). -
Step 1: Write the failing test
In v4/tests/test_host_unit.c, after the check "all five are waiting at their ports" and its block, add:
/* ---- the nodes she births carry a mesh node's ROM (MACHINE.md 2.2) ---- */
CHECK(node_numbered(10)->rom == ROM_IMAGE && node_numbered(11)->rom == MESH_ROM && node_numbered(12)->rom == MESH_ROM
&& node_numbered(13)->rom == MESH_ROM && node_numbered(14)->rom == MESH_ROM,
"Hera carries the host's ROM, and the four she birthed a mesh node's");
CHECK(strcmp(tell(11, "12345 67890 UM* . ."), "0 838102050 ") == 0 && ended_from == 11, "11's UM* is its ROM's: \"%s\"", printed);
CHECK(strcmp(tell(12, "100 0 7 UM/MOD . ."), "14 2 ") == 0 && ended_from == 12, "12's UM/MOD: \"%s\"", printed);
CHECK(strcmp(tell(13, "65536 0 196608 0 Q./ . ."), "0 21845 ") == 0 && ended_from == 13, "13's Q./: \"%s\"", printed);
- Step 2: Run it to verify it fails
Run: make -C v4 build/64-test_host_unit.c && v4/build/64-test_host_unit.c | grep FAIL
Expected: FAIL on Hera carries the host's ROM, and the four she birthed a mesh node's only. The three arithmetic checks pass already, since every node carries the host ROM from Task 2, whose first six entries are the same.
- Step 3: host_born attaches the mesh ROM to every node but Hera
In host_born, the line Task 2 added becomes:
/* Hera, who is made first, is the host's node and carries its ROM; a
* node she births carries a mesh node's (MACHINE.md 2.2). The nucleus
* calls its math at the same entries in either. */
if (k == 0) v4_node_rom_attach(&pool[k].n, ROM_IMAGE, ROM_IMAGE_WORDS);
else v4_node_rom_attach(&pool[k].n, MESH_ROM, MESH_ROM_WORDS);
Update the file's header comment: after "Hera is born empty and is given it through the port her requests go to." add "She carries the host's ROM; a node she births carries a mesh node's, the shared math only (MACHINE.md 2.2), at the same entries."
- Step 4: Run it to verify it passes
Run: make -C v4 build/64-test_host_unit.c && v4/build/64-test_host_unit.c | tail -3
Expected: 0 failures.
- Step 5: Run the suite
Run: make -C v4 test test-32 hosted-check > /tmp/claude-1000/-home-rajames-CLionProjects-LithosAnanake/7f40fa84-d337-4c0c-bdd7-61dc32496f02/scratchpad/suite.txt 2>&1; echo EXIT $?; grep -E 'FAIL|failures' /tmp/claude-1000/-home-rajames-CLionProjects-LithosAnanake/7f40fa84-d337-4c0c-bdd7-61dc32496f02/scratchpad/suite.txt | grep -v ' 0 failures' | head
Expected: EXIT 0, no failures.
- Step 6: Commit
$G add v4/tests/test_host_unit.c
$G commit -m "feat(v4): a node Hera births carries a mesh node's ROM
MACHINE.md 2.2. Hera carries the host's ROM; the four outer nodes the
shared math only, and compute UM*, UM/MOD and Q./ with it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
Task 4: The documents say what is built
Files:
-
Modify:
docs/v4.0.0/DATASHEET-F18.md(§5's ROM paragraph; a new entry list; §13 item 3) -
Modify:
docs/v4.0.0/MACHINE.md(top status; §2.2 status and thepush ;ruling) -
Step 1: The datasheet's entry list
In docs/v4.0.0/DATASHEET-F18.md, the ROM paragraph (it begins "ROM. Built (2026-10-15; MACHINE.md §2): 2049 words") becomes accurate: the host ROM's word count from Task 2's PARITY:V4_ROM words= line or make -C v4 rom's mkrom: line, "The machine starts at its entry 18", and that a mesh node's ROM of N words holds entries 0–5 only (N from mkrom: mesh_rom, N words). Its sentence "This sheet will list each entry's stack effect and the registers it uses once the ROM is built." is replaced by a table, placed directly after that paragraph:
| Entry | Routine | Stack | Uses | In |
|---|---|---|---|---|
| 0 | `UM*` | ( u1 u2 -- ulo uhi ) | A | both ROMs |
| 1 | `UM/MOD` | ( ulo uhi ud -- urem uquot ), exact for uhi < ud | A | both ROMs |
| 2 | `D+` | ( d1 d2 -- d3 ) | — | both ROMs |
| 3 | `DNEGATE` | ( d -- -d ) | — | both ROMs |
| 4 | `Q.*` | ( a b -- c ), Q48.16 | A | both ROMs |
| 5 | `Q./` | ( a b -- q ), Q48.16; by zero: D-11's result and error 11 | A, B; `(Q/)`'s 5 cells | both ROMs |
| 6–17 | the assembler's routines (`capsules/v4/rom/main.f18`) | | | host ROM |
| 18 | `(ROM)`: where the host machine starts | | | host ROM |
Before writing the "Uses" column, check each routine's source in capsules/v4/rom/math.f18 for a!, a, b!, @b, !b (including the helpers it calls) and correct the column to what the code uses.
- Step 2: §13 item 3
Replace item 3's text with: "The ROM is built and the host node boots from it (2026-10-15). A mesh node's ROM is built too (MACHINE.md §2.2): entries 0–5, the shared math, and nothing else. In the test host a node Hera births carries it. The products have no births yet, so they do not link it. A node that is born is still sent the host's assembled system through its port."
-
Step 3: MACHINE.md
-
The status line at the top: replace "The mesh ROM (section 2.2) is designed and not built." with "The mesh ROM (section 2.2) is built."
-
§2.2's
**Status: designed, for review. Not built.**→**Status: built** (the commits of plan docs/v4.0.0/plans/2026-10-15-mesh-rom.md). -
In §2.2's "The system calls the ROM" paragraph, "Each one's code becomes a single
jumpto its ROM entry." → "Each one's code is its entry's address,pushand;: the address goes onto the return stack and;goes there, which is a jump. A branch to a constant is not in the assembler's language (v4/src/text.c), and the ROM's assembler must accept the same language." -
Step 4: Check and commit
Run: grep -n 'entry 12\|single jump\|Not built\|not built yet' docs/v4.0.0/MACHINE.md docs/v4.0.0/DATASHEET-F18.md
Expected: no line about the ROM's start at entry 12, the jump, or the mesh ROM being unbuilt. (Other "not built" lines about other work stay.)
$G add docs/v4.0.0/DATASHEET-F18.md docs/v4.0.0/MACHINE.md
$G commit -m "docs(v4): the ROMs' shared entries, and the mesh ROM as built
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
Task 5: Acceptance
Files:
-
Create:
logs/<timestamp>/{amd64,aarch64,riscv64}/...(by the boots) -
Step 1: Clean tree for the boots
Run: $G status --short disk
Expected: nothing.
- Step 2: Boot each ISA, one at a time, in the foreground
Run, in this order, each to completion before the next, with the session's helper (bash /tmp/claude-1000/-home-rajames-CLionProjects-LithosAnanake/7f40fa84-d337-4c0c-bdd7-61dc32496f02/scratchpad/accept.sh <arch> "65536 0 196608 0 Q./ . ."), which runs make -f kernel/Makefile ARCH=<arch> clean qemu and types the line at ok>:
amd64aarch64riscv64
Expected for each: PARITY:V4_ROM words=<host ROM words> rom_hash=<same on all three>, PARITY:V4_POST tests=538 pass=538 fail=0, POST: PASSED, ok>, the typed line answering 0 21845 ok, and PARITY:V4_SYSTEM ... dict_hash= equal on all three (and equal to Task 2's value).
- Step 3: The 32-bit build
Run: make -C v4 test-32 2>&1 | grep -E 'failures' | grep -v ' 0 failures'; echo done
Expected: only done.
- Step 4: Commit the logs
$G add logs/<amd64 dir> logs/<aarch64 dir> logs/<riscv64 dir>
$G commit -m "logs: the three acceptance boots of the mesh ROM
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
PATH="$PATH:/usr/share/gitkraken/resources/app.asar.unpacked/git/libexec/git-core" $G push -q origin StarForth-v4.0.0
Push after each task's commit too (Tasks 1–4), with the same push command.