fix(v4.0.0): step 6e review round 2 -- only the message looked at is taken, moved or dropped; the kernel's port has no queue in a fabric; an answer nobody awaits is not a loss

- Engine: a wire keeps whether the message at its mark is the one last
  looked at; a node's take, move and drop of any other answer 3 and write
  nothing (MESH.md 7d.3).  A wire cut and made again between a look and a
  take no longer has a NACK done as text, or 256 cells written where one
  was looked for.
- Engine: v4_fabric_wire_kernel wires a kernel to a port with no queues;
  a message for it is refused at once, error 18, on a mesh as on the
  products.
- The result of a drop is looked at; a take that does not take has the
  message dropped, or gone past: nothing goes round.
- An answer nobody waits for is let go uncounted; (LOST) counts what should
  have been delivered or acted on and was not.  The flood's account is
  exact.
- Error 18 from under a word that was printing empties the data stack, as
  a fault does: that word's own values are not left there.
- Tests: the take and drop windows cut at every step, with and without
  another message put; a refusal owed to a stuck node whose wire is full.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-08 22:36:59 -04:00
co-authored by Claude Opus 5.5
parent 6a7d0aa27f
commit f1419e739c
15 changed files with 345 additions and 63 deletions
+76 -13
View File
@@ -165,6 +165,14 @@ static v4_cell ask(unsigned id, v4_cell wire, v4_cell a, v4_cell b, v4_cell op)
return n->doing ? -99 : v4_node_fetch(n, P_HOW);
}
/* The same for a take, a move or a drop, which are done only on the message
* last looked at: the node looks first, as a node does. */
static v4_cell seen(unsigned id, v4_cell wire, v4_cell a, v4_cell b, v4_cell op)
{
(void)ask(id, wire, 500, 0, 1);
return ask(id, wire, a, b, op);
}
int main(void)
{
unsigned a, b, c, i, steps;
@@ -507,6 +515,7 @@ int main(void)
LIT(P_DO); O(BANG_B); LIT(8); O(STORE_B);
LIT(P_HAVE); O(BANG_B); O(FETCH_B); LIT(OUT + 1); O(BANG_A); O(STORE_A);
LIT(P_A); O(BANG_B); LIT(200); O(STORE_B);
LIT(P_DO); O(BANG_B); LIT(1); O(STORE_B); /* it looks, and then takes what it looked at */
LIT(P_B); O(BANG_B); LIT(220); O(STORE_B);
LIT(P_DO); O(BANG_B); LIT(2); O(STORE_B);
LIT(P_HOW); O(BANG_B); O(FETCH_B); LIT(OUT); O(BANG_A); O(STORE_A);
@@ -573,6 +582,7 @@ int main(void)
for (i = 0; i < 7; i++) t[i] = ND(a)->n.mem[100 + i];
for (i = 0; i < 8; i++) t[8 + i] = ND(a)->n.mem[110 + i];
CHECK(v4_wire_put(f.place[a].wire[0].rx, t, t + 8) == V4_WIRE_DONE, "(a message waits)");
f.place[a].wire[0].rx->looked = 1; /* and has been looked at */
memcpy(snap, ND(a)->n.mem, sizeof snap);
memcpy(&before_rx, f.place[a].wire[0].rx, sizeof before_rx);
memcpy(&before_tx, f.place[a].wire[0].tx, sizeof before_tx);
@@ -592,6 +602,7 @@ int main(void)
for (i = 0; i < 7; i++) t[i] = ND(a)->n.mem[100 + i];
for (i = 0; i < 8; i++) t[8 + i] = ND(a)->n.mem[110 + i];
(void)v4_wire_put(f.place[a].wire[0].rx, t, t + 8);
f.place[a].wire[0].rx->looked = 1;
memcpy(snap, ND(a)->n.mem, sizeof snap);
memcpy(&before_rx, f.place[a].wire[0].rx, sizeof before_rx);
CHECK(ask(a, 0, 300, bad[k], 2) == 4 && memcmp(snap, ND(a)->n.mem, sizeof snap) == 0 && memcmp(&before_rx, f.place[a].wire[0].rx, sizeof before_rx) == 0,
@@ -621,9 +632,9 @@ int main(void)
CHECK(ask(a, 0, 200, 220, 7) == 0 && ask(a, 0, 200, 220, 1) == 0 && ND(a)->n.mem[200] == 200, "next: the second");
CHECK(ask(a, 0, 200, 220, 2) == 0 && msg_is(&ND(a)->n, 200, 220, 21, 2), "take of the second");
CHECK(ask(a, 0, 200, 220, 1) == 0 && ND(a)->n.mem[200] == 300, "the mark is at the third");
CHECK(ask(a, 0, 200, 220, 6) == 0 && ask(a, 0, 200, 220, 4) == 0 && ask(a, 0, 200, 220, 1) == 0 && ND(a)->n.mem[200] == 300, "first and drop: the first is gone, the third is left");
CHECK(ask(a, 0, 200, 220, 6) == 0 && seen(a, 0, 200, 220, 4) == 0 && ask(a, 0, 200, 220, 1) == 0 && ND(a)->n.mem[200] == 300, "first and drop: the first is gone, the third is left");
CHECK(ask(a, 0, 200, 220, 7) == 3 && ask(a, 0, 200, 220, 1) == 3, "next at the end answers 3 and the mark is at no message");
CHECK(ask(a, 0, 200, 220, 6) == 0 && ask(a, 0, 200, 220, 2) == 0 && msg_is(&ND(a)->n, 200, 220, 4, 3) && f.place[a].wire[0].rx->used == 0, "and the third is taken");
CHECK(ask(a, 0, 200, 220, 6) == 0 && seen(a, 0, 200, 220, 2) == 0 && msg_is(&ND(a)->n, 200, 220, 4, 3) && f.place[a].wire[0].rx->used == 0, "and the third is taken");
/* "which wires have a message": a bit to a wire; fetching it clears arrived */
v4_fabric_init(&f, places, 4);
@@ -720,9 +731,9 @@ int main(void)
for (i = 0; i < 100; i++) (void)v4_fabric_step(&f);
CHECK(ND(a)->n.doing, "and still blocked");
v4_wire_first(f.place[b].wire[3].rx);
CHECK(ask(b, 3, 400, 420, 4) == 0, "the reader drops the front message (which is 263 cells)");
CHECK(seen(b, 3, 400, 420, 4) == 0, "the reader drops the front message (which is 263 cells)");
while (ND(a)->n.doing && f.place[b].wire[3].rx->used > 0) {
(void)ask(b, 3, 400, 420, 4);
(void)seen(b, 3, 400, 420, 4);
}
CHECK(!ND(a)->n.doing && ND(a)->n.how == 0 && f.place[a].wire[0].tx->used + 200 <= V4_WIRE_CELLS, "woken, with how it went 0");
/* a small message in front: dropping it frees little, and the sleeper wants more than that leaves */
@@ -742,12 +753,12 @@ int main(void)
(void)v4_fabric_step(&f);
CHECK(ask(a, 0, 250, 0, 9) == -99, "asleep for 250");
v4_wire_first(f.place[b].wire[3].rx);
CHECK(ask(b, 3, 400, 420, 4) == 0 && V4_WIRE_CELLS - f.place[a].wire[0].tx->used == 200, "the reader drops the small one: 200 are free");
CHECK(seen(b, 3, 400, 420, 4) == 0 && V4_WIRE_CELLS - f.place[a].wire[0].tx->used == 200, "the reader drops the small one: 200 are free");
CHECK(!ND(a)->n.doing && ND(a)->n.how == 1, "the sleeper, which wants 250, is woken, and told 1: a wire of its own has gained room, and not enough");
CHECK(ask(a, 0, 250, 0, 9) == -99, "it asks again, and sleeps");
for (i = 0; i < 50; i++) (void)v4_fabric_step(&f);
CHECK(ND(a)->n.doing, "and sleeps on while nothing changes");
CHECK(ask(b, 3, 400, 420, 4) == 0 && !ND(a)->n.doing && ND(a)->n.how == 0, "the next take gives it the room: 0");
CHECK(seen(b, 3, 400, 420, 4) == 0 && !ND(a)->n.doing && ND(a)->n.how == 0, "the next take gives it the room: 0");
/* woken by a message coming instead */
v4_wire_reset(f.place[a].wire[0].tx);
fill(f.place[a].wire[0].tx, 100);
@@ -775,14 +786,14 @@ int main(void)
for (i = 0; i < 100; i++) (void)v4_fabric_step(&f);
CHECK(ND(a)->n.doing, "and it sleeps on while nothing changes");
v4_wire_first(f.place[c].wire[2].rx);
CHECK(ask(c, 2, 400, 420, 4) == 0, "the reader of wire 1 drops a message");
CHECK(seen(c, 2, 400, 420, 4) == 0, "the reader of wire 1 drops a message");
CHECK(!ND(a)->n.doing && ND(a)->n.how == 1 && f.place[a].wire[0].tx->used == V4_WIRE_CELLS, "it wakes, told 1: the wire it named has no room, but something has changed");
CHECK(ask(a, 0, 100, 0, 10) == 1 && ask(a, 1, 100, 0, 10) == 0, "it asks which: wire 1 has the room");
CHECK(ask(a, 0, 100, 0, 9) == -99, "asleep for room on wire 0 again: that room on wire 1 was seen");
for (i = 0; i < 100; i++) (void)v4_fabric_step(&f);
CHECK(ND(a)->n.doing, "and it is not woken twice for it");
/* the room comes in the very step the node goes to sleep: the reader's take is done before the sleep is looked at */
v4_node_store(&ND(c)->n, P_WIRE, 2); v4_node_store(&ND(c)->n, P_DO, 4);
(void)ask(c, 2, 500, 0, 1); v4_node_store(&ND(c)->n, P_WIRE, 2); v4_node_store(&ND(c)->n, P_DO, 4);
ND(a)->n.doing = 0;
v4_node_store(&ND(a)->n, P_WIRE, 0); v4_node_store(&ND(a)->n, P_A, 100); v4_node_store(&ND(a)->n, P_DO, 9);
(void)v4_fabric_step(&f);
@@ -802,7 +813,58 @@ int main(void)
(void)v4_node_fetch(&ND(a)->n, P_HAVE);
(void)v4_fabric_step(&f);
CHECK(ask(a, 0, 0, 0, 8) == -99, "asleep, for a message");
CHECK(ask(c, 2, 400, 420, 4) == 0 && ND(a)->n.doing, "room on a wire does not wake a node that sleeps for a message only");
CHECK(seen(c, 2, 400, 420, 4) == 0 && ND(a)->n.doing, "room on a wire does not wake a node that sleeps for a message only");
/* TAKE, MOVE AND DROP ARE ABOUT THE MESSAGE LAST LOOKED AT (MESH.md 7d.3). A node looks, decides, and
* then takes: if the wire was cut and made again in between, and another message put, what is at the
* mark is not what it looked at. Then each answers 3, no such message, and nothing is written or moved. */
v4_fabric_init(&f, more_places, 8);
v4_fabric_queues(&f, qpool, 8);
a = (unsigned)v4_fabric_add(&f, &pool[0], PB);
b = (unsigned)v4_fabric_add(&f, &pool[1], PB);
c = (unsigned)v4_fabric_add(&f, &pool[2], PB);
CHECK(v4_fabric_wire(&f, a, 0, b, 3) && v4_fabric_wire(&f, a, 1, c, 2), "a node between two");
put_msg(&ND(b)->n, 100, 110, 4, 1);
CHECK(ask(b, 3, 100, 110, 5) == 0 && ask(a, 0, 0, 0, 6) == 0, "a message of four characters comes, and the mark is at it");
memcpy(snap, ND(a)->n.mem, sizeof snap);
CHECK(ask(a, 0, 200, 220, 2) == 3 && ask(a, 0, 1, 0, 3) == 3 && ask(a, 0, 0, 0, 4) == 3 && f.place[a].wire[0].rx->used == 8 && memcmp(snap, ND(a)->n.mem, sizeof snap) == 0,
"not looked at: take, move and drop each answer 3, and nothing is written or taken");
CHECK(ask(a, 0, 200, 220, 1) == 0 && ND(a)->n.mem[206] == 4, "it is looked at: four characters");
/* the wire is cut and made again, and a message of 1,024 characters put where the short one was */
CHECK(v4_fabric_unwire(&f, a, 0) && v4_fabric_wire(&f, a, 0, b, 3), "the wire is cut, and made again");
put_msg(&ND(b)->n, 100, 110, 1024, 2);
CHECK(ask(b, 3, 100, 110, 5) == 0 && f.place[a].wire[0].rx->used == 263, "and a message of 1,024 characters is put on it");
memcpy(snap, ND(a)->n.mem, sizeof snap);
CHECK(ask(a, 0, 200, 220, 2) == 3 && memcmp(snap, ND(a)->n.mem, sizeof snap) == 0 && f.place[a].wire[0].rx->used == 263,
"a take answers 3: it is not the message that was looked at. Nothing is written: not one cell, where 256 would have been");
CHECK(ask(a, 0, 1, 0, 3) == 3 && ask(a, 0, 0, 0, 4) == 3 && f.place[a].wire[0].rx->used == 263 && f.place[a].wire[1].tx->used == 0, "nor is it moved, or dropped");
CHECK(ask(a, 0, 200, 220, 1) == 0 && ND(a)->n.mem[206] == 1024 && ask(a, 0, 200, 220, 2) == 0 && msg_is(&ND(a)->n, 200, 220, 1024, 2), "looked at, it is taken");
/* a look is good for one take, move or drop, and not after the mark has been moved */
put_msg(&ND(b)->n, 100, 110, 4, 3); CHECK(ask(b, 3, 100, 110, 5) == 0, "another comes");
put_msg(&ND(b)->n, 100, 110, 8, 4); CHECK(ask(b, 3, 100, 110, 5) == 0, "and another behind it");
CHECK(ask(a, 0, 200, 220, 1) == 0 && ask(a, 0, 0, 0, 7) == 0 && ask(a, 0, 200, 220, 2) == 3, "looked at, then the mark moved on with next: a take answers 3");
CHECK(ask(a, 0, 0, 0, 6) == 0 && ask(a, 0, 200, 220, 1) == 0 && ask(a, 0, 0, 0, 6) == 0 && ask(a, 0, 0, 0, 4) == 3, "looked at, then first: a drop answers 3");
CHECK(ask(a, 0, 200, 220, 1) == 0 && ask(a, 0, 0, 0, 4) == 0 && ask(a, 0, 0, 0, 4) == 3 && f.place[a].wire[0].rx->used == 9, "looked at and dropped; the one that followed has not been looked at, and is not dropped");
CHECK(ask(a, 0, 200, 220, 1) == 0 && ND(a)->n.mem[206] == 8 && ask(a, 0, 1, 0, 3) == 0 && f.place[a].wire[1].tx->used == 9, "looked at, it is moved");
/* a message put behind the one looked at does not undo the look */
put_msg(&ND(b)->n, 100, 110, 4, 5); CHECK(ask(b, 3, 100, 110, 5) == 0 && ask(a, 0, 200, 220, 1) == 0, "one comes and is looked at");
put_msg(&ND(b)->n, 100, 110, 4, 6); CHECK(ask(b, 3, 100, 110, 5) == 0 && ask(a, 0, 200, 220, 2) == 0 && msg_is(&ND(a)->n, 200, 220, 4, 5), "another comes behind it: the first is still the one looked at, and is taken");
/* THE KERNEL ON A PORT has no queue: it is asked by words, and takes no messages */
{
static stream ks;
static v4_device kern;
unsigned free_before = f.queue_free;
ks.took_n = 0; kern.take = stream_take; kern.give = 0; kern.self = &ks; kern.pending = 0;
CHECK(v4_fabric_wire_kernel(&f, a, 5, &kern) && f.queue_free == free_before && !f.place[a].wire[5].rx && !f.place[a].wire[5].tx, "a kernel is wired to a port, and is given no queues");
put_msg(&ND(a)->n, 100, 110, 4, 7);
CHECK(ask(a, 5, 100, 110, 5) == 2 && ask(a, 5, 8, 0, 10) == 2 && ask(a, 5, 8, 0, 9) == 2 && ask(a, 5, 200, 220, 1) == 2 && ask(a, 5, 0, 0, 6) == 2 && !ND(a)->n.doing,
"a message put to it, room asked of it, a sleep for room on it, a look: each answers 2 at once -- no one takes messages there");
v4_node_store(&ND(a)->n, PB + 5, 77);
(void)v4_fabric_step(&f);
CHECK(!ND(a)->n.asking && ks.took_n == 1 && ks.took[0] == 77, "a word written to it is handed over as before");
CHECK(v4_fabric_unwire(&f, a, 5) && f.queue_free == free_before, "and cutting it gives back no queue that it never had");
}
/* move from one of a node's wires to another */
v4_fabric_init(&f, more_places, 8);
@@ -814,19 +876,20 @@ int main(void)
put_msg(&ND(b)->n, 100, 110, 21, 4);
ND(b)->n.mem[103] = 9;
CHECK(ask(b, 3, 100, 110, 5) == 0 && ask(a, 0, 0, 0, 6) == 0, "a message comes on wire 0");
CHECK(ask(a, 0, 1, 0, 3) == 0, "and is moved to wire 1");
CHECK(seen(a, 0, 1, 0, 3) == 0, "and is moved to wire 1");
CHECK(f.place[a].wire[0].rx->used == 0 && f.place[c].wire[2].rx->used == 7 + 6, "it is off the one and on the other");
CHECK(ask(c, 2, 200, 220, 6) == 0 && ask(c, 2, 200, 220, 2) == 0 && ND(c)->n.mem[203] == 8 && msg_is_but(&ND(c)->n, 200, 220, 21, 4, 3),
CHECK(ask(c, 2, 200, 220, 6) == 0 && seen(c, 2, 200, 220, 2) == 0 && ND(c)->n.mem[203] == 8 && msg_is_but(&ND(c)->n, 200, 220, 21, 4, 3),
"and arrives whole, its fourth word one fewer");
/* the other full: 1, and nothing moved */
put_msg(&ND(b)->n, 100, 110, 21, 4);
CHECK(ask(b, 3, 100, 110, 5) == 0 && ask(a, 0, 0, 0, 6) == 0, "another comes");
fill(f.place[a].wire[1].tx, 0);
(void)ask(a, 0, 500, 0, 1);
memcpy(&copy, f.place[a].wire[0].rx, sizeof copy);
{
v4_wire_queue copy2;
memcpy(&copy2, f.place[a].wire[1].tx, sizeof copy2);
CHECK(ask(a, 0, 1, 0, 3) == 1, "moved to a wire with no room: 1");
CHECK(ask(a, 0, 1, 0, 3) == 1, "looked at, and moved to a wire with no room: 1");
CHECK(memcmp(&copy, f.place[a].wire[0].rx, sizeof copy) == 0 && memcmp(&copy2, f.place[a].wire[1].tx, sizeof copy2) == 0, "and neither wire has changed");
}
CHECK(ask(a, 0, 5, 0, 3) == 2 && ask(a, 0, -1, 0, 3) == 2 && ask(a, 5, 1, 0, 3) == 2, "move to a port with nothing, or from one: 2");
@@ -872,7 +935,7 @@ int main(void)
for (w = 0; w < 7; w++) h[w] = ND(a)->n.mem[100 + w];
for (w = 0; w < 5; w++) t[w] = ND(a)->n.mem[110 + w];
CHECK(v4_fabric_device_put(&f, a, 4, h, t) == 0, "the device puts a message");
CHECK(ask(a, 4, 200, 220, 6) == 0 && ask(a, 4, 200, 220, 2) == 0 && msg_is(&ND(a)->n, 200, 220, 17, 8), "and the node takes it, whole");
CHECK(ask(a, 4, 200, 220, 6) == 0 && seen(a, 4, 200, 220, 2) == 0 && msg_is(&ND(a)->n, 200, 220, 17, 8), "and the node takes it, whole");
put_msg(&ND(a)->n, 100, 110, 3, 9);
CHECK(ask(a, 4, 100, 110, 5) == 0, "the node puts one");
memset(h, 0, sizeof h); memset(t, 0, sizeof t);
+4 -2
View File
@@ -273,6 +273,7 @@ static int one(unsigned path, const char *what, unsigned depth, const char *text
v4_cell lost11 = pool[mid].n.mem[LOST], refused12 = pool[far].n.mem[REFUSED], ways12;
int how11, works = 0, clean_error = 0;
unsigned left;
v4_uheat_t clock12;
if (!sixhundred[0]) memset(sixhundred, 'A', 600);
runs++;
@@ -281,6 +282,7 @@ static int one(unsigned path, const char *what, unsigned depth, const char *text
ways12 = pool[far].n.mem[ROUTE_COUNT];
forget();
saw_room_wait = 0; saw_passed = 0;
clock12 = pool[far].es.anticlock;
if (why) { }
else if (path == 4) { /* node 12 is busy for a long while and sends no answer; node 11 waits; its centre says 12 is gone */
stuck = 1u << far;
@@ -319,8 +321,8 @@ static int one(unsigned path, const char *what, unsigned depth, const char *text
switch (path) {
case 0: works = how11 == V4_TEXT_COMPLETED && strcmp(printed, sixhundred) == 0 && left == values;
clean_error = over && is_cut_short(printed, sixhundred, over); break;
case 1: works = how11 == V4_TEXT_COMPLETED && printed_len == 0 && left == values && pool[mid].n.mem[LOST] == lost11 + 1; /* node 12's answer, which it did not wait for */
clean_error = over && strcmp(printed, over) == 0 && pool[mid].n.mem[LOST] == lost11; break;
case 1: works = how11 == V4_TEXT_COMPLETED && printed_len == 0 && left == values && pool[far].es.anticlock != clock12 && pool[mid].n.mem[LOST] == lost11; /* node 12 did the text */
clean_error = over && strcmp(printed, over) == 0 && pool[far].es.anticlock == clock12; break; /* nothing was sent */
case 2: works = how11 == V4_TEXT_ERROR && strcmp(printed, "Argument out of range\n") == 0 && left == values;
clean_error = over && strcmp(printed, over) == 0; break;
case 3: works = how11 == V4_TEXT_COMPLETED && printed_len == 0 && left == values && pool[mid].n.mem[LOST] == lost11;
+156 -13
View File
@@ -39,6 +39,10 @@ static v4_wire_queue queues[QUEUES];
/* ---- the console: a device on a port ---- */
#define CONSOLE_ID 1
static const v4_device console = { 0, 0, 0, 0 }; /* it takes and gives no words: only whole messages */
/* a kernel on a node's port 0: it takes the words written to it, and no messages (MESH.md 7d.5) */
static unsigned kernel_words;
static int kernel_take(void *self, v4_cell value) { (void)self; (void)value; kernel_words++; return 1; }
static const v4_device kernel_dev = { kernel_take, 0, 0, 0 };
static unsigned hera, mid, far; /* where the three nodes are: 10, 11, 12 */
static char printed[32768]; /* what has come back since the console last sent */
static unsigned printed_len;
@@ -227,6 +231,10 @@ static void put_back(void) { memcpy(pool, kept_pool, sizeof pool); memcpy(places
static int far_has_one(void) { return coming_to(far, 2) && coming_to(far, 2)->used > 0; }
static int far_has_nack(void) { return far_has_one() && coming_to(far, 2)->cell[(coming_to(far, 2)->head + 2u) % V4_WIRE_CELLS] == V4_MSG_NACK; }
static int far_awaits_and_has_one(void) { return pool[far].n.mem[AWAIT_FROM] == 11 && far_has_one(); }
static v4_cell mid_for; /* whom the message waited for at node 11's wire from node 12 is for */
static int mid_has_one(void) { return coming_to(mid, 3) && coming_to(mid, 3)->used > 0 && coming_to(mid, 3)->cell[coming_to(mid, 3)->head] == mid_for; }
static v4_cell far_lost_wanted;
static int far_has_lost(void) { return pool[far].n.mem[LOST] >= far_lost_wanted && asleep(far); }
static int mid_holds(void) { return pool[mid].n.doing && pool[mid].n.do_op == 9 && pool[mid].n.mem[H_PORT] >= 0; }
static unsigned saw_mid_hold;
static void watch_mid_holds(void) { if (pool[mid].n.doing && pool[mid].n.do_op == 9 && pool[mid].n.mem[H_PORT] >= 0) saw_mid_hold++; }
@@ -317,7 +325,8 @@ int main(void)
CHECK(pool[hera].n.mem[LOST] == (v4_cell)i + 1, "and that node has counted it");
CHECK(strcmp(tell(11, "(LOST) @ ."), "0 ") == 0 && strcmp(tell(12, "(LOST) @ ."), "0 ") == 0, "the others have lost nothing");
i = (unsigned)pool[hera].n.mem[LOST];
CHECK(told(10, ": TELL2 S\" 1 DROP\" 12 SEND ; TELL2") && pool[hera].n.mem[LOST] == (v4_cell)i + 1, "an answer that comes to a node that is not waiting for it is let go, and counted");
CHECK(told(10, ": TELL2 S\" 1 DROP\" 12 SEND ; TELL2") && pool[hera].n.mem[LOST] == (v4_cell)i && coming_to(hera, 2)->used == 0,
"an answer that comes to a node that is not waiting for it is let go, and is not counted: a SEND that is not waited on is ordinary");
/* ---- waiting ---- */
{
@@ -358,10 +367,8 @@ int main(void)
const char *r = tell(10, ": GO S\" 6 10 BURST 6 12 BURST\" 11 SEND S\" 6 11 BURST 6 10 BURST\" 12 SEND 6 11 BURST 6 12 BURST ; GO");
CHECK(is_empty(r), "six messages from each node to each other, all at once: \"%s\"", r);
CHECK(asleep(hera) && asleep(mid) && asleep(far), "all three come to rest");
/* each has let go, and counted, the answers to what it sent, which it did not wait for: node 10 to its
* 12 messages and its two texts, and two from before and the message for 99; the others to 12 */
CHECK(strcmp(tell(10, "GOT @ . (LOST) @ ."), "12 17 ") == 0 && strcmp(tell(11, "GOT @ . (LOST) @ ."), "12 12 ") == 0 && strcmp(tell(12, "GOT @ . (LOST) @ ."), "12 13 ") == 0,
"every one of them arrived; what each has counted is the answers it did not wait for: \"%s\"", printed);
CHECK(strcmp(tell(10, "GOT @ . (LOST) @ ."), "12 1 ") == 0 && strcmp(tell(11, "GOT @ . (LOST) @ ."), "12 0 ") == 0 && strcmp(tell(12, "GOT @ . (LOST) @ ."), "12 0 ") == 0,
"every one of them arrived, and none was let go: \"%s\"", printed);
}
/* ---- THE FLOOD: more than the wires hold (MESH.md 7d.6, acceptance 2) ----
@@ -369,12 +376,13 @@ int main(void)
* word that sends ends there, error 19, and the refusal is counted. A message a middle node has no room
* to pass on is let go there, counted, and its sender told. Nothing waits, and it comes to rest. */
{
long got = 0, lost = 0, refused = 0, sent = 0, tried = 0, v;
long got = 0, lost = 0, refused = 0, sent = 0, tried = 0, v, let_go, orders = 0;
unsigned k, q, left = 0;
static const v4_cell who[3] = { 10, 11, 12 };
const char *r;
CHECK(is_empty(tell(10, "0 GOT ! 0 SENT ! 0 TRIED !")) && is_empty(tell(11, "0 GOT ! 0 SENT ! 0 TRIED !")) && is_empty(tell(12, "0 GOT ! 0 SENT ! 0 TRIED !")), "the counts begin again");
r = tell(10, ": GO2 S\" 200 10 BURST 200 12 BURST\" 11 SEND S\" 200 11 BURST 200 10 BURST\" 12 SEND 200 11 BURST 200 12 BURST ; GO2");
for (k = 0; k < 3; k++) { lost -= pool[k].n.mem[LOST]; refused -= pool[k].n.mem[REFUSED]; }
r = tell(10, ": GO2 S\" 200 12 BURST 200 10 BURST\" 11 SEND S\" 200 10 BURST 200 11 BURST\" 12 SEND 200 12 BURST 200 11 BURST ; GO2");
CHECK(strcmp(r, "(still running)") != 0, "two hundred from each to each: it ends");
CHECK(asleep(hera) && asleep(mid) && asleep(far), "and all three come to rest, asleep");
for (k = 0; k < 3; k++) for (q = 0; q < V4_PORTS; q++) if (coming_to(k, q)) left += coming_to(k, q)->used;
@@ -383,15 +391,24 @@ int main(void)
v = strtol(tell(who[k], "GOT @ ."), NULL, 10); got += v;
CHECK(v >= 0 && v <= 400, "node %ld did %ld of the messages for it", (long)who[k], v);
sent += strtol(tell(who[k], "SENT @ ."), NULL, 10);
tried += strtol(tell(who[k], "TRIED @ ."), NULL, 10);
v = strtol(tell(who[k], "TRIED @ ."), NULL, 10); tried += v;
if (v == 0) orders++; /* it never began to send: the text that told it to was itself let go on the way */
lost += pool[k].n.mem[LOST];
refused += pool[k].n.mem[REFUSED];
}
printf(" the flood: %ld sends tried, %ld on a wire, %ld refused at once; %ld done; (LOST) %ld, (REFUSED) %ld in all\n", tried, sent, tried - sent, got, lost, refused);
/* A SEND refused at once ends the word that sends, so the flood holds itself back: what got on a wire
* is what there was room for. (LOST) counts, with what was let go on the way, every answer that
* came to a node that did not wait for it, so it is not a count of what was lost. */
CHECK(tried > sent && sent >= got && got > 0 && refused >= tried - sent, "there was not room for all of them: some were refused at once, and counted");
let_go = sent - got;
printf(" the flood: %ld sends tried: %ld refused at once, %ld let go on the way, %ld done; %ld of the two texts that set a node sending let go; (LOST) %ld, (REFUSED) %ld\n",
tried, tried - sent, let_go, got, orders, lost, refused);
/* THE ACCOUNT IS EXACT. Every send tried was refused at once (counted once, in (REFUSED), and the word
* that sent ended there); or got on a wire and was done; or got on a wire and was let go on the way by
* a node with no room to pass it on -- counted there in (LOST), and once more where its NACK ended: in
* (REFUSED) at the node that sent it, or in (LOST) if the NACK could not go either. That is so too of the
* text that sets a node sending, which is one more message on those wires. Nothing else is
* counted: an answer waits for room and is not let go, and one that nobody waits for is not a loss. */
CHECK(tried > sent && got > 0 && let_go >= 0, "there was not room for all of them: some were refused at once");
CHECK(lost + refused == (tried - sent) + 2 * (let_go + orders), "every message is accounted for, exactly: (LOST) + (REFUSED) is %ld; %ld refused at once and twice %ld let go is %ld",
lost + refused, tried - sent, let_go + orders, (tried - sent) + 2 * (let_go + orders));
CHECK(let_go > 0, "and some were let go on the way, so that is not an account of nothing");
CHECK(strcmp(tell(12, "7 8 * ."), "56 ") == 0 && printed_from == 12, "and the nodes go on as before");
}
@@ -449,6 +466,41 @@ int main(void)
CHECK(strcmp(tell(12, "7 8 * ."), "56 ") == 0 && printed_from == 12 && strcmp(tell(10, "1 2 + ."), "3 ") == 0, "and it and node 10 answer: \"%s\"", printed);
}
/* the same with the wire to the stuck node full: the NACK cannot go, and is counted */
CHECK(triangle(), "the same made afresh");
{
v4_cell lost;
v4_uheat_t c;
char text[64];
CHECK(told(12, "NO-ROUTES 1 3 ROUTE 10 3 ROUTE 11 2 ROUTE : F 0 DO S\" HIT\" 11 SEND LOOP ;") && told(11, "55 3 ROUTE"), "node 12 has no way for 55, and a word that sends node 11 short texts");
stuck = 1u << mid;
(void)tell(11, ": C2 400000 0 DO LOOP S\" 1 DROP\" 55 SEND BEGIN 0 UNTIL ; C2");
snprintf(text, sizeof text, "%u F", (unsigned)(V4_WIRE_CELLS / 8u));
CHECK(told(12, text) && going_from(far, 2)->used == V4_WIRE_CELLS, "node 11 is busy; node 12 fills the wire to it");
lost = pool[far].n.mem[LOST];
far_lost_wanted = lost + 1;
CHECK(run_until(far_has_lost, 30000000) && run() && pool[far].n.mem[LOST] == lost + 2 && going_from(far, 2)->used == V4_WIRE_CELLS && coming_to(far, 2)->used == 0,
"node 11 then sends 55 a message by node 12 and sticks: node 12 lets it go, and the NACK it owes has no room on the wire: both are counted: %ld", (long)(pool[far].n.mem[LOST] - lost));
c = clock_of(far);
for (i = 0; i < 500; i++) (void)v4_fabric_step(&f);
CHECK(asleep(far) && clock_of(far) == c && strcmp(tell(12, "7 8 * ."), "56 ") == 0 && strcmp(tell(10, "1 2 + ."), "3 ") == 0, "node 12 is asleep, owing nothing, and it and node 10 answer");
}
/* ---- THE KERNEL ON A NODE'S PORT TAKES NO MESSAGES (MESH.md 7d.5, ruled 2026-10-08) ---- */
CHECK(triangle() && v4_fabric_wire_kernel(&f, far, 0, &kernel_dev), "the same made afresh, with a kernel on node 12's port 0");
{
kernel_words = 0;
CHECK(strcmp(tell(12, "7 0 PORT! 1 2 + ."), "3 ") == 0 && kernel_words == 1, "a word written to the kernel's port is taken, as ever");
CHECK(strstr(tell(12, ": S0 S\" 1 DROP\" 0 0 SEND-ON ; S0"), "No one on that port") != NULL && ended == 1 && ended_how == V4_TEXT_ERROR && kernel_words == 1,
"a message sent by that port is refused at once: \"%s\"", printed);
CHECK(told(12, "1 0 ROUTE"), "node 12 is told the way to its console is the kernel's port");
CHECK(is_empty(tell(12, "65 EMIT")) && ended == 1 && ended_from == 12 && ended_how == V4_TEXT_ERROR && asleep(far),
"a text that prints cannot send what it printed: it ends in an error, said by the wire the text came on, and node 12 sleeps");
CHECK(is_empty(tell(12, ": P 3000 0 DO I . LOOP ; P")) && ended == 1 && ended_how == V4_TEXT_ERROR && asleep(far) && pool[far].n.ds.depth == 0,
"one that prints more than the buffer holds ends in error 18 and does not wait: %ld", (long)pool[far].n.mem[NODE_ERROR]);
CHECK(told(12, "1 NO-ROUTE") && strcmp(tell(12, "7 8 * ."), "56 ") == 0 && kernel_words == 1, "told to forget that way, it prints again; the kernel was sent nothing");
}
/* ---- acceptance 5: PRINTING IS NOT LOST ---- */
CHECK(triangle(), "the same made afresh");
{
@@ -566,6 +618,7 @@ int main(void)
"node 11 is holding output, asleep for room");
CHECK(v4_fabric_remove(&f, mid) == &pool[1] && run(), "node 11 is removed, and the two that are left come to rest");
CHECK(asleep(far) && pool[far].n.mem[NODE_ERROR] == 18 && pool[far].n.mem[LINE_STATUS] == 2 && pool[far].n.mem[KEEP] == -1, "node 12's text has ended in error 18, No one on that port, and it sleeps");
CHECK(pool[far].n.ds.depth == 0, "with nothing left on its data stack of what the word that was printing had there: %u", (unsigned)pool[far].n.ds.depth);
CHECK(strncmp(printed, want, printed_len) == 0 && strcmp(tell(10, "1 2 + ."), "3 ") == 0 && asleep(hera), "what had arrived was in order, and node 10 goes on");
/* the node that prints removed while the middle node holds its output: what is on the wires arrives */
@@ -655,6 +708,96 @@ int main(void)
CHECK(wrong == 0 && cut_before > 20 && cut_after > 0, "a GONE: cut before it was taken (%u times) no way was forgotten; cut after (%u), one: %u wrong", cut_before, cut_after, wrong);
}
/* ---- ONLY WHAT WAS LOOKED AT IS TAKEN (fix round 2, NP1; MESH.md 7d.3) ----
* Between the look and the take the wire is cut, made again, and a different message put on it. */
{
unsigned k, tried = 0, wrong = 0, hung = 0;
static v4_message big;
static char blanks[1025];
/* text was looked at; a NACK is there now. The NACK must not be done as text: it is a refusal, and counted as one. */
CHECK(row() && told(12, "VARIABLE N : T1 1 N +! ; : T2 10 N +! ;") && told(12, "T1") && put_text(12, "T2") && run_until(far_has_one, 1000000), "text has come to node 12's wire");
keep();
for (k = 0; k < 200; k++) {
static const v4_cell hd[V4_WIRE_HEADER] = { 12, 11, V4_MSG_NACK, 16, 0, 0, 4 }, t[1] = { 'T' | ('2' << 8) };
v4_cell refused;
put_back();
refused = pool[far].n.mem[REFUSED];
for (i = 0; i < k; i++) { (void)v4_fabric_step(&f); (void)console_takes(); }
if (!far_has_one()) break;
if (!v4_fabric_unwire(&f, mid, 3) || !v4_fabric_wire(&f, mid, 3, far, 2) || v4_wire_put(places[far].wire[2].rx, hd, t) != V4_WIRE_DONE) { wrong += 1000; break; }
tried++;
if (!run()) { hung++; continue; }
if (strcmp(tell(12, "N @ ."), "1 ") != 0 || pool[far].n.mem[REFUSED] != refused + 1 || pool[far].n.mem[ME] != 12) wrong++;
}
CHECK(tried > 100 && wrong == 0 && hung == 0, "text looked at, and a NACK there when it comes to take: at each of %u steps the NACK was counted as a refusal and nothing was done as text: %u wrong, %u hung", tried, wrong, hung);
/* a NACK was looked at; a text of 1,024 characters is there now. Its 256 cells must not be taken where one was looked for. */
memset(blanks, ' ', 1024);
CHECK(row() && told(12, ": N77 S\" 1 DROP\" 77 SEND ;") && put_text(12, "N77") && run_until(far_has_nack, 1000000) && v4_message_text(&big, 12, 11, V4_MSG_TEXT, blanks, 1024), "a NACK has come to node 12's wire");
keep();
tried = wrong = hung = 0;
for (k = 0; k < 200; k++) {
static v4_cell before[V4_NODE_WORDS];
v4_cell at;
unsigned changed = 0;
put_back();
for (i = 0; i < k; i++) { (void)v4_fabric_step(&f); (void)console_takes(); }
if (!far_has_nack()) break;
memcpy(before, pool[far].n.mem, sizeof before);
if (!v4_fabric_unwire(&f, mid, 3) || !v4_fabric_wire(&f, mid, 3, far, 2) || v4_wire_put(places[far].wire[2].rx, big.word, big.word + V4_MSG_HEADER) != V4_WIRE_DONE) { wrong += 1000; break; }
tried++;
if (!run()) { hung++; continue; }
/* the cells after (ONE), as far as the variables go, are as they were: the text went to TIB, where text goes */
for (at = ONE + 1; at < BUF0_W; at++) if (at != LINE_STATUS && at != QUIET && at != DONE_PORT && at != KEEP && at != REPLY && !(at >= MSG && at < MSG + 7) && at != PRINT_TO && at != PRINT_PORT
&& !(at >= S_ON && at <= S_HAVE) && at != DEFER && pool[far].n.mem[at] != before[at]) changed++;
if (changed || pool[far].n.mem[ME] != 12 || strcmp(tell(12, "1 2 + ."), "3 ") != 0) wrong++;
}
CHECK(tried > 100 && wrong == 0 && hung == 0, "a NACK looked at, and a text of 1,024 characters there when it comes to take: at each of %u steps nothing was overwritten, and node 12 answers: %u wrong, %u hung", tried, wrong, hung);
}
/* ---- A DROP THAT DOES NOT DROP (fix round 2, NP3) ----
* The wire is cut between the look and the drop: the cut let the message go, and nothing is counted or told for it. */
{
unsigned k, cut_before = 0, cut_after = 0, wrong = 0;
/* (REFUSE): node 11 has no way for what node 12 sends 99 */
mid_for = 99;
CHECK(row() && told(11, "NO-ROUTES 1 2 ROUTE 10 2 ROUTE 12 3 ROUTE") && told(12, ": S99 S\" 1 DROP\" 99 SEND ;") && put_text(12, "S99") && run_until(mid_has_one, 1000000), "a message node 11 must refuse has come to its wire");
keep();
for (k = 0; k < 320; k++) {
int there;
v4_cell lost;
put_back();
lost = pool[mid].n.mem[LOST];
for (i = 0; i < k; i++) { (void)v4_fabric_step(&f); (void)console_takes(); }
there = mid_has_one();
(void)v4_fabric_unwire(&f, mid, 3);
if (!run() || !asleep(mid)) { wrong += 1000; break; }
if (there) cut_before++; else cut_after++;
/* cut after the drop the message is counted; and its NACK too, if the cut came before that was put */
if (there ? pool[mid].n.mem[LOST] != lost : !(pool[mid].n.mem[LOST] == lost + 1 || pool[mid].n.mem[LOST] == lost + 2)) wrong++;
}
CHECK(wrong == 0 && cut_before > 20 && cut_after > 0, "a refusal: cut before the drop (%u times) nothing was counted; cut after (%u), the message, and its NACK if it could not go: %u wrong", cut_before, cut_after, wrong);
/* (IDLE): what node 12 prints comes to node 11, which is not a console: it is let go, and counted */
cut_before = cut_after = wrong = 0;
mid_for = 11;
CHECK(row() && told(12, "11 (CONSOLE) !") && put_text(12, "65 EMIT") && run_until(mid_has_one, 1000000), "what a node printed has come to the wire of a node that is no console");
keep();
for (k = 0; k < 320; k++) {
int there;
v4_cell lost;
put_back();
lost = pool[mid].n.mem[LOST];
for (i = 0; i < k; i++) { (void)v4_fabric_step(&f); (void)console_takes(); }
there = mid_has_one();
(void)v4_fabric_unwire(&f, mid, 3);
if (!run() || !asleep(mid)) { wrong += 1000; break; }
if (there) cut_before++; else cut_after++;
if (pool[mid].n.mem[LOST] != lost + (there ? 0 : 1)) wrong++;
}
CHECK(wrong == 0 && cut_before > 20 && cut_after > 0, "what should not have come: cut before the drop (%u times) nothing was counted; cut after (%u), one: %u wrong", cut_before, cut_after, wrong);
}
/* ---- Review Focus 4: THE ANSWER HAS ROOM KEPT ----
* Node 11 sends the console 127 short messages by way of node 12, which passes them on to the wire to
* node 10 while node 10 is busy: that wire is then full but for 8 cells. Node 10 has sent node 12 text
+3 -3
View File
@@ -219,12 +219,12 @@ static int wire_do(void)
case 1: if (rx) how = span_ok(a, V4_WIRE_HEADER) || rx->mark >= rx->used ? v4_wire_look(rx, span_ok(a, V4_WIRE_HEADER) ? &n.mem[a] : hd) : V4_WIRE_NOT_A_MESSAGE; break;
case 2:
if (!rx) break;
if (v4_wire_look(rx, hd) != V4_WIRE_DONE) { how = V4_WIRE_NO_MESSAGE; break; }
if (!rx->looked || v4_wire_look(rx, hd) != V4_WIRE_DONE) { how = V4_WIRE_NO_MESSAGE; break; }
cells = v4_wire_cells(hd[6]) - V4_WIRE_HEADER;
how = span_ok(a, V4_WIRE_HEADER) && span_ok(b, cells) ? v4_wire_take(rx, &n.mem[a], cells ? &n.mem[b] : hd, cells) : V4_WIRE_NOT_A_MESSAGE;
break;
case 3: if (rx) how = rx->mark >= rx->used ? V4_WIRE_NO_MESSAGE : a == (v4_cell)CONSOLE_PORT ? v4_wire_move(rx, &wire_out) : V4_WIRE_NO_ONE; break;
case 4: if (rx) how = v4_wire_drop(rx); break;
case 3: if (rx) how = rx->mark >= rx->used || !rx->looked ? V4_WIRE_NO_MESSAGE : a == (v4_cell)CONSOLE_PORT ? v4_wire_move(rx, &wire_out) : V4_WIRE_NO_ONE; break;
case 4: if (rx) how = rx->looked ? v4_wire_drop(rx) : V4_WIRE_NO_MESSAGE; break;
case 5:
if (!tx) break;
cells = span_ok(a, V4_WIRE_HEADER) ? v4_wire_cells(n.mem[a + 6]) : 0;
+6 -5
View File
@@ -143,7 +143,7 @@ static int host_born(void *self)
v4_blocks_init(&node_blocks[k], BUF0_W);
if (k != 0) { /* Hera's kernel is wired where she is made */
node_kernel[k].take = node_kernel_take; node_kernel[k].give = 0; node_kernel[k].self = &pool[k]; node_kernel[k].pending = 0;
if (!v4_fabric_wire_device(&f, (unsigned)place, 0, &node_kernel[k])) return -1;
if (!v4_fabric_wire_kernel(&f, (unsigned)place, 0, &node_kernel[k])) return -1; /* it takes words, and no messages */
}
return place;
}
@@ -405,7 +405,7 @@ int main(void)
hera_place = (unsigned)place;
for (i = 0; i < 4096 && pool[0].n.mem[i] == 0; i++) { }
CHECK(i == 4096 && pool[0].n.p == PORT + (v4_cell)V4_PORT_ANY, "with nothing in it, listening at its ports");
CHECK(v4_fabric_wire_device(&f, hera_place, 0, &kernel) && v4_fabric_wire_device(&f, hera_place, 1, &console), "the kernel is on its port 0 and the console on its port 1");
CHECK(v4_fabric_wire_kernel(&f, hera_place, 0, &kernel) && v4_fabric_wire_device(&f, hera_place, 1, &console), "the kernel is on its port 0 and the console on its port 1");
for (i = 0; i < 400000 && v4_fabric_step(&f) != 0; i++) { }
CHECK(given_cells == f18_count && pool[0].n.doing && pool[0].n.do_op == 8 && !pool[0].n.stopped,
"it takes in the nucleus through its port, a word at a time, and sleeps until a message comes: %u of %u cells", given_cells, f18_count);
@@ -490,8 +490,7 @@ int main(void)
CHECK(strcmp(tell(11, ": X S\" 66 EMIT\" 14 SEND ; X"), "B") == 0 && printed_from == 14, "and to the one across from it, which it is not wired to: \"%s\"", printed);
CHECK(clock_of(10) > hera, "that went by a node in between");
}
CHECK(strcmp(tell(10, "(LOST) @ ."), "0 ") == 0 && strcmp(tell(11, "(LOST) @ ."), "2 ") == 0 && strcmp(tell(14, "(LOST) @ ."), "0 ") == 0,
"nothing was let go but the two answers that came back to node 11, which did not wait for them: \"%s\"", printed);
CHECK(strcmp(tell(10, "(LOST) @ ."), "0 ") == 0 && strcmp(tell(11, "(LOST) @ ."), "0 ") == 0 && strcmp(tell(14, "(LOST) @ ."), "0 ") == 0, "nothing was let go");
/* ---- they share the store (acceptance 3) ---- */
step_limit = 30000000ul;
@@ -515,7 +514,7 @@ int main(void)
CHECK(told(10, ": GO S\" W\" 12 SEND W ; GO"), "Hera sets node 12 writing and writes her own meanwhile: \"%s\"", printed);
for (i = 0; i < 10 && common_dev[(12 + i) * V4_BLOCK_BYTES] == 'a' && common_dev[(22 + i) * V4_BLOCK_BYTES] == 'b'; i++) { }
CHECK(i == 10, "all twenty blocks reached the store: the first that did not is %u", i);
CHECK(strcmp(tell(10, "(LOST) @ ."), "1 ") == 0 && strcmp(tell(12, "(LOST) @ ."), "0 ") == 0, "and no message was let go but node 12's answer to Hera, who did not wait for it: \"%s\"", printed);
CHECK(strcmp(tell(10, "(LOST) @ ."), "0 ") == 0 && strcmp(tell(12, "(LOST) @ ."), "0 ") == 0, "and no message was let go");
CHECK(waiting(10) && waiting(11) && waiting(12) && waiting(13) && waiting(14), "all five are waiting at their ports again");
/* ---- Hera kills a node by number, and tells the others it is gone (MESH.md 7b, 7d) ----
@@ -548,6 +547,8 @@ int main(void)
CHECK(waiting(12) && waiting(11) && waiting(10), "every node that is not stuck is asleep again");
CHECK(strcmp(tell(12, "12 100 * ."), "1200 ") == 0 && strcmp(tell(11, "11 100 * ."), "1100 ") == 0 && strcmp(tell(10, "1 2 + ."), "3 ") == 0, "and everything else keeps running: \"%s\"", printed);
CHECK(strstr(tell(12, "5 3 PORT!"), "No one on that port") != NULL && waiting(12), "a write to the port node 14 was on is an error at once, not a wait: \"%s\"", printed);
CHECK(strstr(tell(12, ": K0 S\" 1 DROP\" 0 0 SEND-ON ; K0"), "No one on that port") != NULL && strcmp(tell(12, "2100 BLOCK C@ ."), "65 ") == 0,
"a message sent by the port node 12's kernel is on is refused at once -- the kernel takes no messages -- and its blocks are asked for as before: \"%s\"", printed);
{
v4_cell refused = node_numbered(12)->mem[REFUSED], r11 = node_numbered(11)->mem[REFUSED];
CHECK(tell(12, ": AGAIN14 S\" 65 EMIT\" 14 SEND ; AGAIN14")[0] == 0 && node_numbered(12)->mem[REFUSED] == refused + 1 && waiting(12),