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
+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