The mistakes, published
A competent engineer, holding the rule in mind — publish only what you alone know; derive the rest — rebuilt the Communicator's rate loop six times in a day, and the first five were protocols wearing tuple-space clothes. This page is each one, and how you spot it. It should save you your own twelve hours.
You will reach for a protocol. Not because you missed the point — because the reflex is thirty years deep and it does not care whether you understood. This page is one instance of the programming surface →
The job was one loop: three participants on a call, one rate, no negotiation. Here is what the reflex produced instead — each mistake named by its shape, because the shapes recur far beyond video.
Each end measured its inbound rate and wrote a request to the other. Two ends doing that on matched two-second windows is a control loop with no damping. It hunted for an hour at 23.8 of 24 frames a second with zero drops — nothing was failing; the two controllers were arguing.
Removed the request; each end capped itself on its own measurement. No loop — but a sender now knows only about the link to the peer it happens to be receiving from, and on a three-way call it needs the slowest. The fact others needed was computed, then kept local.
So each end published what it could take and senders read the minimum. Better — and still a protocol in the read direction: a headless publisher with no viewers became "the slowest consumer" on its own call and walked a good link down to 160×120 on evidence that did not exist.
So: two roles, producer and consumer. Which stamped a label on a participant — and writing a receive report relabelled a two-way end as "consumer," so its own send rate stopped counting. The one end nobody could be bounded by was the one that both sends and receives.
So a request tuple — "I want 640×360" — written by a consumer and honoured by a sender. Which is a negotiation with the word filed off: two parties, a message, and one waiting on the other. Putting a request in the memory does not stop it being a request.
And underneath several of the five: a loop whose input was influenced by its own last decision — a cap set from a measurement the cap itself had shaped. A controller that eats its own exhaust converges on nothing in particular, and looks like a flaky link while it does it.
Only the sixth is UnREST. A sender writes what it is sending; a receiver writes what it is getting and whether it is keeping up; every participant reads all of it and computes the rate with the same pure function — no clock, no self, no I/O. They all get the same answer because they are all reading the same rows and running the same arithmetic. Not because they agreed. There was never an exchange to agree in.
The function is printed in full on the demonstrations page — forty lines you can check by eye for the message that isn't there. Read derive_rate →
Could this be a single process with threads, and would anything about it change?
If yes, you have written a protocol you did not need. In one process you would never write the rate rule twice, once per participant — there would be one function and every thread would call it. The moment there was one function, the two ends stopped disagreeing: not because they synchronised, but because there was nothing left to disagree about.