feat(v4.0.0): the nucleus is a capsule, sent to a node born empty

MESH.md step 2.  A capsule of F18 code is the words a neighbour writes to a
node's port: for each stretch of memory, "@p a! @p push", the address and
count, "@p !+ unext" and the words; then a jump to the start.  A node born
empty executes that from its port, so it needs nothing in it beforehand.

- capsule.h/.c: v4_capsule_write, any node's memory as such a capsule
- mkimage writes the nucleus so, to capsules/v4/nucleus-64.f18, and the
  addresses a host needs as a C file; the memory image is no longer linked
  into either product
- mkcapsule is unchanged: the nucleus capsule is a built file kept under
  capsules/, as BLOCK_MAP.md is, and is baked, hashed and signed with the
  rest
- boot: the node is born empty (v4_image_born); the nucleus capsule is
  found, its hash and signature checked, and given to the node a word at a
  time as it reads its port; PARITY:V4_NUCLEUS carries its name and hash

Verified: test_fabric.c (59 checks, both widths, and under ASan and UBSan):
a memory with a programme and scattered words arrives word for word in an
empty node and runs.  The nucleus capsule rebuilds byte for byte.
hosted-check passes on three ISAs; clean qemu with STARFORTH_V4=1 on amd64,
aarch64 and riscv64 takes the nucleus in, passes POST (550 of 550) and
answers lines typed at each prompt (logs/20261006-102421, -102706,
-103048).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-06 10:33:15 -04:00
co-authored by Claude Opus 5.5
parent 9af442f793
commit 42610e844f
17 changed files with 718 additions and 88 deletions
+44
View File
@@ -6,6 +6,7 @@
* At the level of opcodes: no word of any vocabulary is defined here.
*/
#include "v4/asm.h"
#include "v4/capsule.h"
#include "v4/fabric.h"
#include <stdio.h>
#include <string.h>
@@ -69,6 +70,13 @@ static int stream_take(void *self, v4_cell value)
return 1;
}
/* for v4_capsule_write: each word of the capsule, into the stream's own list */
static void collect(void *self, v4_cell word)
{
stream *s = (stream *)self;
((v4_cell *)s->words)[s->count++] = word;
}
/* What a neighbour sends an empty node to put `count` words at `addr` and
* start there (MESH.md section 5): for the F18, and for this node, code a
* node executes straight from its port. */
@@ -187,6 +195,42 @@ int main(void)
CHECK(ND(b)->n.last_from == 0, "it was listening to any port, and knows which it heard from: %u", ND(b)->n.last_from);
}
/* ---- a capsule of F18 code: any node's memory, sent to an empty node (capsule.h) ---- */
{
static v4_node src;
static v4_cell sent[400];
static stream s;
static v4_device dev;
unsigned k;
v4_node_reset(&src);
v4_asm_begin(&as, &src, 40); /* a programme, not at the start of memory */
LIT(123); LIT(OUT); O(BANG_A); O(STORE_A);
wait_for_ever();
CHECK(v4_asm_ok(&as), "a programme for the capsule");
src.mem[3] = 5; /* and words here and there: the first of memory, */
src.mem[200] = -1; src.mem[202] = 7; /* two with one zero between them, */
src.mem[300] = 9; src.mem[310] = 11; /* two with many, */
src.mem[PB - 1] = 13; /* and the last before the ports */
memset(&s, 0, sizeof s);
s.words = sent;
dev.give = stream_give; dev.take = stream_take; dev.self = &s;
s.count = 0;
s.count = v4_capsule_write(&src, 40, collect, &s);
CHECK(s.count > 0 && s.count < 400, "the capsule is %u words for a memory of %u", s.count, (unsigned)V4_NODE_WORDS);
s.at = 0;
v4_fabric_init(&f, places, 4);
b = (unsigned)v4_fabric_add(&f, &pool[0], PB);
CHECK(v4_fabric_wire_device(&f, b, 5, &dev), "it is sent to a node newly born");
steps = settle(5000);
CHECK(s.at == s.count, "which takes all of it: %u of %u", s.at, s.count);
for (k = 0; k < V4_NODE_WORDS && ND(b)->n.mem[k] == (k == (unsigned)OUT ? 123 : src.mem[k]); k++) { }
CHECK(k == V4_NODE_WORDS, "its memory is then the same, word for word (first difference at %u)", k);
CHECK(ND(b)->n.mem[OUT] == 123 && ND(b)->n.reading, "and it was started where the capsule says, and ran");
}
/* ---- a word is passed on by a node in between ---- */
v4_fabric_init(&f, places, 4);
a = loaded(0);