Specification §14
Two adaptations, two mechanisms, and they MUST NOT be conflated. The key words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY are to be interpreted as described in RFC 2119.
FrogNet Specification — Draft 0.9, revision 2026-08-29. Cite a conformance claim against this revision, not against a section number alone.
FrogNet adapts in two independent ways and an implementation MUST NOT conflate them.
| Semantic adaptation | Media adaptation | |
|---|---|---|
| Applies to | Every exchange | The media plane only (SotF) |
| Mechanism | Send only what changed (§11) | Trade fidelity for continuity (§14.3) |
| Trigger | Next-hop RTT, per hop (§11.5) | What participants publish about themselves |
| Saving is | Structural | Deliberate degradation |
The saving from BLDC-1 is structural, not statistical: the payload is not sent because both ends already hold it (§11.10). An implementation MUST NOT quote a compression ratio for the codec itself. A ratio describes a workload, not a mechanism, and the same mechanism yields any ratio depending on how much of the traffic is repetition.
Where measured figures are published they MUST carry their workload and their conditions, and the set MUST include a negative control — a workload the mechanism does badly on — or the figures describe only the cases chosen to flatter it.
Latency figures MUST be published alongside compression figures for the same run. Compression that costs round-trip time has moved a cost rather than removed one, and a reader is entitled to see both from one measurement.
SotF (§1.3, §15) trades fidelity for continuity as the link tightens. Nine rungs, higher index richer, verified in sotf_ladder.py:
idx code name kind needs camera 8 L8 BULLFROG video+audio yes 7 L7 CHORUS video+audio yes 6 L6 ENSEMBLE video+audio yes 5 L5 DUET video+audio yes 4 L4 SOLO audio no (needs mic) 3 L3 VOICE audio no (needs mic) 2 L2 WHISPER text no 1 L1 BEACON text no 0 L0 PULSE presence no
Audio is mono: AUDIO_CH = 1 at 16 kHz, Opus (fnphone_pa.py:43–44). The docstring at the head of sotf_ladder.py says stereo and is wrong; an implementation MUST NOT reproduce it.
A video rung needs a camera and only a camera [VIDEO_DOES_NOT_NEED_A_MIC_V1]. A machine with a camera and no microphone sends video with no audio bed — a stream with a note on it, not a disqualification — and the receiver is told there is no audio and plays what there is. Gating video rungs on a microphone made a camera-only box compute a text-only ceiling: a working webcam produced text presence.
The two lowest rungs are a floor and MUST NOT be removed from the available set.
Adding a rung MUST extend the top, never renumber. The index travels on the wire, so an existing index must keep its meaning. A peer built before a new top rung existed mislabels it in reporting and decodes the picture correctly, because on receive the index is used for reporting only — the decoder reads geometry from the bitstream.
Rate control is two separable things, and an implementation MUST keep them apart. Conflating them is what makes the purity requirement below look impossible to satisfy alongside the hold-downs in §14.4.3.
| Stage | Is | May consult |
|---|---|---|
| Target derivation | What rate the published state implies, right now | The published rows, and nothing else |
| Adoption | Whether and when to move to that target | Elapsed time and the currently adopted rate |
Target derivation MUST be a pure function of published state: no clock, no instance state, no I/O [ONE_DERIVATION_V1]. Every participant runs the same function over the same rows and lands on the same answer without being told, which is what makes agreement unnecessary rather than merely cheap (§10.6.2). Being pure is also what makes it testable in isolation.
Adoption is a local policy and MAY consult the clock, because it decides only when this participant moves — not what the answer is. Two participants applying different hold-downs converge on the same target at different moments; two participants deriving different targets do not converge at all. An implementation MUST NOT feed an adoption decision, or the currently adopted rate, back into the derivation (§10.6.2): a derivation that consults its own last answer is a controller with memory, and two controllers with memory diverge.
[A_RATE_THAT_FAILED_IS_NOT_A_CANDIDATE_V1] A rule that promotes whenever every participant is currently content will promote into a rate already shown not to work, become discontent, demote, become content, and promote again.
The evidence a consumer publishes MUST therefore be more than a present verdict. It is the geometry and frame rate that consumer is actually receiving, so that a report of struggling at a size establishes that the size does not work on that link. From which:
the ceiling is the smallest size at which anybody is struggling; nothing may promote to or above it.
A derivation with no memory of what failed produced ninety-odd oscillations in five seconds in simulation. That memory MUST live in the published state as a row every participant derives the same ceiling from — not in a controller's private history, because two controllers with memory diverge (§10.6.2).
Zero frames arriving means the producer sent nothing. It does not mean this consumer is slow, and reading it as slowness is a ratchet: the producer sheds, the consumer reports worse, the rate drops, the producer sheds more (§10.6.3).
Damping lives in adoption (§14.4), never in derivation. Rate changes MUST be damped in both directions, and asymmetrically: a slower hold before climbing than before dropping. Continuity is for the user, who must never be asked to intervene; a picture that thins has not failed. Visible failure is for the operator, who must never be lied to. An implementation that hides a degradation from its log is not transparent, merely quiet.
The media relay forwards one stream and bounds the call rate to the slowest participant, so a fast participant is held to a slow one for as long as both are in the call (§17.10). This stands until the relay can send a client something other than what arrived, and an implementation MUST NOT claim per-client adaptation while it holds.
Every published figure MUST carry its provenance, and figures of different provenance MUST NOT be presented as though they were the same kind of thing:
| Kind | Means |
|---|---|
| Verified in source | A constant read at its line |
| Recorded run | An instrumented measurement, with its workload stated |
| Read off a diagnostic | Observed on camera, not from an instrument log — publish a bound, not a point figure |
| Estimate | A did-versus-would comparison against published third-party figures. MUST be labelled as an estimate. |
A figure whose provenance cannot be established MUST NOT be published. Where a check reports a rate above 100% or calls a figure sourced that is not, the check is wrong, not the data — fix the check.
Specification §10.6, §11, §11a, §15, §16, §17. Source: etc/frognet_bundles/communicator/sotf_ladder.py, fnphone_pa.py, comms_control.py, fnav.py. Build manual: Croakus §49, §50, §53. Measured results: the simulator.