fix(v4.0.0): step 6c mended after its review -- what a stuck or gone node can and cannot hold up

The review of step 6c found it did not keep its rule.  Ruled: fix them
all; 16 hops.

- Paying a NACK no longer waits on a lower-numbered neighbour; a node
  that owes one does not sleep, and goes on taking in what it is sent.
- AWAIT looks first through the messages already waiting.
- A node waiting to write to a removed neighbour gets error 18: the
  word that says who is reading also says which ports have anything on
  them.
- A console line lets Hera go when she is blocked on the first word of
  a message (v4_fabric_interrupt_error, a device's `pending`).
- A refused answer is told to the node that waits, not the one that
  answered; one owed to the node itself goes to its own queue.
- A message is passed on by at most 16 nodes, then refused.
- A GONE is believed only when it says it is from the node's centre.
- Tests: the check that tested nothing is replaced; no room for a
  waiting sender; the limit that remains is tested as a limit.
- MESH.md 7b's rule reworded to what holds, 7b.7 says where it falls
  short; README likewise.

make -C v4 test, sanitize, hosted-check; three bare-metal boots.  POST
538 of 538, word_count=317, dict_hash=0xc0769523a47b7dc3 on all six.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-07 22:18:06 -04:00
co-authored by Claude Opus 5.5
parent d343001dea
commit 59727f762a
19 changed files with 1058 additions and 66 deletions
+15
View File
@@ -26,6 +26,7 @@ typedef struct {
int (*take)(void *self, v4_cell value); /* the node wrote `value`: 1 if it is taken, 0 if not yet */
int (*give)(void *self, v4_cell *value); /* the node is reading: 1 with a word in *value, 0 if not yet */
void *self;
int (*pending)(void *self); /* 1 if it has a word to give that has not been asked for; may be 0: it never says */
} v4_device;
/* One end of a wire. */
@@ -55,6 +56,7 @@ typedef struct {
v4_place *place;
unsigned capacity;
v4_cell gone_error; /* see v4_fabric_gone_error; 0: none */
v4_cell interrupt_error; /* see v4_fabric_interrupt_error; 0: none */
} v4_fabric;
/* An empty fabric with room for `capacity` nodes, in `places`. */
@@ -71,6 +73,19 @@ void v4_fabric_init(v4_fabric *f, v4_place *places, unsigned capacity);
* is how a fabric begins, nothing is raised. */
void v4_fabric_gone_error(v4_fabric *f, v4_cell code);
/* A NODE BLOCKED WRITING TO A NODE THAT DOES NOT READ. Of two neighbours
* one may write to the other without looking (docs/v4.0.0/MESH.md 7a), and
* is then blocked until the other reads; if the other never does, it is
* blocked for ever, and if it is the node the console is on, nothing can be
* typed to it again. So: when a node that can take an error is blocked
* writing to another node, on the first word of a message (node.h,
* v4_node_words_since_look: nothing of the message has gone), and a device
* on one of its own ports says it has something pending for it, the error
* `code` is raised on the node: it gives up that message and goes on, to
* read what the device has. Only a device with a `pending` function can
* do this. With `code` 0, which is how a fabric begins, it never happens. */
void v4_fabric_interrupt_error(v4_fabric *f, v4_cell code);
/* More room: `places`, of `capacity` no less than before, takes over; every
* node keeps its number and its wiring. */
void v4_fabric_grow(v4_fabric *f, v4_place *places, unsigned capacity);
+5
View File
@@ -65,6 +65,11 @@ typedef struct {
* port". */
#define V4_ERROR_NO_ONE 18
/* The nucleus's error for a node let go of a message it was blocked on the
* first word of, because its console has a line for it (fabric.h,
* v4_fabric_interrupt_error): "Interrupted". */
#define V4_ERROR_INTERRUPTED 21
/* The description of the nucleus built from v4/capsule (the generated file
* defines it). */
extern const v4_image v4_capsule_image;
+13 -1
View File
@@ -111,6 +111,7 @@ typedef struct {
unsigned last_from; /* the port the last read from "any" took its word from */
unsigned writers; /* bit k: the neighbour on port k is blocked writing to this node */
unsigned readers; /* bit k: the neighbour on port k is blocked reading from this node */
unsigned since_look; /* words taken from this node since it last fetched either of those */
/* DSTACK-DEPTH and RSTACK-DEPTH (D-16). See v4_node_stack_regs_attach. */
v4_cell dstack_reg; /* its word address, or -1 */
@@ -181,7 +182,11 @@ void v4_node_console_attach(v4_node *n, v4_cell addr);
* base + V4_PORTS + 1 which port the last read from "any" came from
* base + V4_PORTS + 2 which ports have a neighbour waiting to write to this
* node: bit k for port k
* base + V4_PORTS + 3 which ports have a neighbour waiting to read from it
* base + V4_PORTS + 3 which ports have a neighbour waiting to read from it,
* bit k for port k; and, V4_PORTS bits higher, which
* ports have anything on them at all: bit V4_PORTS + k
* for port k. A port with nothing on it can never
* be read from or written to.
* The last two are how a node looks before it writes (MESH.md 7a), as the
* F18's io register lets it. They are what whoever connects the ports last
* said, v4_node_port_status; fetching them waits for nothing and changes
@@ -214,6 +219,13 @@ void v4_node_port_attach(v4_node *n, v4_cell base);
/* The neighbour has taken what the node wrote. */
void v4_node_port_served(v4_node *n);
/* How many words the node has had taken since it last looked at its
* neighbours (a fetch of either of the two status addresses). A node that
* looks before it begins a message (MESH.md 7a) and is blocked writing with
* this at 0 is blocked on the first word of the message: nothing of it has
* gone. */
unsigned v4_node_words_since_look(const v4_node *n);
/* THERE IS NO ONE ON THE PORT the node is blocked at -- writing to it, or
* reading from that one port -- and there will not be. A node that can
* take an error (it has an error register and a fault table: node.h,