From 473e105a692c5f43126671b6e98bc87f661bb96d Mon Sep 17 00:00:00 2001 From: rajames Date: Thu, 15 Oct 2026 19:23:31 -0400 Subject: [PATCH] docs(v4): the mesh ROM's implementation plan Co-Authored-By: Claude Opus 5.5 --- docs/v4.0.0/plans/2026-10-15-mesh-rom.md | 876 +++++++++++++++++++++++ 1 file changed, 876 insertions(+) create mode 100644 docs/v4.0.0/plans/2026-10-15-mesh-rom.md diff --git a/docs/v4.0.0/plans/2026-10-15-mesh-rom.md b/docs/v4.0.0/plans/2026-10-15-mesh-rom.md new file mode 100644 index 00000000..ab6ebaf1 --- /dev/null +++ b/docs/v4.0.0/plans/2026-10-15-mesh-rom.md @@ -0,0 +1,876 @@ +# 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.0` in 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 with `PATH="$PATH:/usr/share/gitkraken/resources/app.asar.unpacked/git/libexec/git-core" $G push -q origin StarForth-v4.0.0`. Commit messages end `Co-Authored-By: Claude Opus 5.5 `. +- 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*`, 1 `UM/MOD`, 2 `D+`, 3 `DNEGATE`, 4 `Q.*`, 5 `Q./`; 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= clean qemu` for amd64, aarch64 and riscv64, one QEMU at a time in the foreground, each to `ok>` with `PARITY:V4_POST tests=538 pass=538 fail=0` and `POST: PASSED`, `PARITY:V4_SYSTEM dict_hash=` equal across the three; and `make -C v4 test-32` with 0 failures. Logs are committed plain. `git status --short disk` must 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 ;`, not `jump ROM-xxx`.** The spec says "a single `jump`". The text assembler refuses a branch to a constant ("not something to branch to", `v4/src/text.c` `branch`), 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 in `text.c` and in the ROM's assembler, and six one-line edits. +- **`host_load` (`v4/tests/host_map.h`) attaches the host ROM**, so every `test_host_*` test links the ROM. Without it, a system that `text.c` builds for a test faults at its first `UM*`. `mkmap` includes `host_map.h` but cannot link the ROM (the ROM is built from mkmap's output), so the attach is compiled only where `V4_ROM_LINKED` is 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 rom` builds it at both widths. Cost if wrong: one object in `kernel/Makefile`. + +## Review Focus + +1. **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 a `hosted-check` line in Task 2. +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). +3. **32-bit cells.** Every table in Task 1 has its 32-bit values; `make -C v4 test-32` runs them. +4. **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. +5. **Return-stack depth through the trampoline** at the deepest Q48 chains (`Q.LOG` → `Q./` → `(UQ/)` → `D2*C`). The trampoline's `push` is 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.f18` in 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)` with `X(index, routine, forth, effect)`; `V4_ROM_MATH_COUNT` (6); `V4_ROM_START` (18). +- Produces: arrays `v4_rom_[]`/`v4_rom__words` (unchanged names) and `v4_mesh_rom_[]`/`v4_mesh_rom__words`; files `$(BINDIR)/rom-.{c,sym}` and `$(BINDIR)/mesh-rom-.{c,sym}`. +- Produces: in `rom_harness.h`, `MESH_ROM`, `MESH_ROM_WORDS`; the test define `V4_MESH_ROM_SYM`. + +- [ ] **Step 1: Write the failing test** + +Create `v4/tests/test_host_rom_math.c`: + +```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`: + +```c +/* 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*C` becomes `(D2*C)`; +- every call `DNEGATE` becomes `(DNEGATE)`; +- the call `(UQ/)` in `DIV:` 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;`: + +```c + /* 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__[]` and `v4___words`. Concretely: +- header comment line `mkrom OUT.c OUT.sym MAP.f18 SOURCE.f18...` → `mkrom NAME OUT.c OUT.sym MAP.f18 SOURCE.f18...`, and `OUT.c the ROM: const v4_cell v4_rom_[] and its length` → `OUT.c the ROM: const v4_cell v4__[] 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]`, every `argv[2]` (the symbol file) → `argv[3]`, and the loop `for (i = 3; ...)` → `for (i = 4; ...)`; +- the two `fprintf` lines 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], ...` with `argv[2]` for the output file. + +- [ ] **Step 8: The Makefile builds both ROMs** + +In `v4/Makefile`: +- `ROM_FILES` gains `math.f18` after `dir.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`, the `mkmap` and `mkrom` prerequisites gain `$(HERE)/include/v4/rom_entries.h` only 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: + +```make +$(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-.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_IMAGE` block, add: + +```c +/* 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: + +```c +#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/-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** + +```bash +$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 " +``` + +--- + +### Task 2: The system calls the ROM + +**Files:** +- Modify: `v4/tests/host_map.h` (ROM-entry constants; `host_load` attaches the host ROM) +- Modify: `v4/tools/mkmap.c` (`mkmap sys` writes the ROM-entry constants) +- Modify: `v4/Makefile` (every `test_host_*` links the ROM and defines `V4_ROM_LINKED`; a `hosted-check` line) +- Modify: `v4/tests/rom_harness.h` (the ROM externs move to `host_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_born` attaches the host ROM) +- Test: `v4/tests/test_host_rom_math.c` + +**Interfaces:** +- Consumes: `V4_ROM_MATH_ENTRIES`, `MESH_ROM`, `ROM_IMAGE` from 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 in `host_load`; `host_load` leaves the host ROM attached. + +- [ ] **Step 1: Write the failing test** + +Append to `v4/tests/test_host_rom_math.c`, before `int main`: + +```c +/* 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: + +```c +/* 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`: + +```c +#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 ... #endif` block from `rom_harness.h` into `host_map.h`, unchanged, above `host_load`; +- in `host_load`, after `HOST_CONSTANTS(HOST_CONSTANT)`, add `HOST_ROM_ENTRY_CONSTANTS(HOST_CONSTANT)`; and after `v4_node_stack_regs_attach(n, DSTACK_REG, RSTACK_REG);` add: + +```c +#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 (through `and 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 (through `C1: pop pop + 1 + ;`) → `: D+ ROM-D+ push ; \ ( d1 d2 -- d3 ) the ROM's entry 2` +- `: DNEGATE ( d -- -d )` and its body (through `L1: 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 )` through `push 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 )` through ` Z0: drop jump (QERR11)` → `: Q./ ROM-Q./ push ; \ ( a b -- q ) the ROM's entry 5`; +- delete `: (QERR11) NODE-ERROR b! 11 !b ;` (only `Q./` 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: + +```c + 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`): + +```make + @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> $`) 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** + +```bash +$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 " +``` + +--- + +### 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: + +```c + /* ---- 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: + +```c + /* 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** + +```bash +$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 " +``` + +--- + +### 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 the `push ;` 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: + +```markdown +| 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 `jump` to its ROM entry." → "Each one's code is its entry's address, `push` and `;`: 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.) + +```bash +$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 " +``` + +--- + +### Task 5: Acceptance + +**Files:** +- Create: `logs//{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 "65536 0 196608 0 Q./ . ."`), which runs `make -f kernel/Makefile ARCH= clean qemu` and types the line at `ok>`: +- `amd64` +- `aarch64` +- `riscv64` + +Expected for each: `PARITY:V4_ROM words= rom_hash=`, `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** + +```bash +$G add logs/ logs/ logs/ +$G commit -m "logs: the three acceptance boots of the mesh ROM + +Co-Authored-By: Claude Opus 5.5 " +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.