aboutsummaryrefslogtreecommitdiffstats
path: root/docs
diff options
context:
space:
mode:
Diffstat (limited to 'docs')
-rw-r--r--docs/MESHBAY_DESIGN.md36
1 files changed, 36 insertions, 0 deletions
diff --git a/docs/MESHBAY_DESIGN.md b/docs/MESHBAY_DESIGN.md
index 555d0f1..892b747 100644
--- a/docs/MESHBAY_DESIGN.md
+++ b/docs/MESHBAY_DESIGN.md
@@ -2099,6 +2099,39 @@ MSE string) fed to a source buffer, with the node holding one slot per viewer.
driven by a clock and by playback and **never by arriving data**. Granting a
credit per append pulls at network speed, fills the buffer ceiling, and then goes
silent for the length of the accumulated balance.
+- **The read-ahead bound is a byte budget, stated in seconds.** What a browser
+ limits is bytes, so a bound written in seconds has to be sized for the
+ highest-bitrate file in the library and every ordinary file then holds a
+ fraction of what the same browser would have taken — measured against a
+ 100 MB ceiling, a 1.0 Mbit/s film reached its ninety seconds on 20.7 MB and a
+ 2.3 Mbit/s one on 45.6 MB. On a mobile network that unspent ceiling is the
+ whole of the margin there is for a dead spot. The budget is therefore in
+ bytes, converted with the file's own average bitrate, and the fixed bound is
+ its **floor and never its ceiling**: nothing pulls less than it used to.
+- **A ceiling that varies by engine and device is walked up to, not declared.**
+ The two ways to find a limit nobody publishes are to overshoot it or to
+ approach it, and overshooting costs a refused append every time it is tried:
+ a flat budget against a smaller ceiling measured 1386 refusals on a film that
+ had none. So the budget never exceeds one step past the most read-ahead that
+ SourceBuffer has actually accepted, and a refusal takes the ceiling down
+ below where it broke and leaves it there — backing off and climbing again is
+ a sawtooth with a refused append at every peak.
+- **The budget only grows while the film is playing.** What it buys is a film
+ that survives a dead spot, and a film nobody has started cannot be
+ interrupted. Left to grow on the loading screen it filled a small engine's
+ whole buffer — 40 MB where the fixed bound took 8 — with film on a connection
+ that is paying for it, and reached a ceiling the fixed bound never came near.
+ Autoplay is blocked on a phone more often than not, so that is the ordinary
+ first state of every playback. Pausing does not shrink it: the lead already
+ paid for is the viewer's.
+- **An append refused for quota is not retried until something frees room.** It
+ will be refused again for as long as the answer has not changed, and the
+ pipeline's clock calls the append path several times a second: the retry
+ alone cost minutes of refusals while the film played far enough for eviction
+ to have anything to drop. Eviction is the only thing that gives a
+ SourceBuffer room back, so its success is what lifts the hold — not a timer,
+ and not the playhead, which moves long before the answer changes. This cannot
+ deadlock: a full buffer is one holding film the playhead has not reached.
- **Credit follows the buffer, decided in one place.** The append path grants
nothing, because the buffer's update event fires for evictions too — crediting
from it pays the node for the player's own housekeeping.
@@ -3136,6 +3169,9 @@ process runs it — `systemctl --user` on Linux, Task Scheduler on Windows.
| The exact-hash content check | Structural, not functional (§7.5) |
| **QUIC** | Off by default, and **not at parity**: it serves the index and file chunks with no transfer lease, no leaseless ceiling and no root-availability check, does its file I/O on the event loop, and returns exception text to the peer (**L3**). No client speaks it. Either it comes to parity or it goes; until then §5.1's "chat is the only gap" is the one sentence here that overstates the code |
| **The relay registry** | **Closed in the code**: `relay.RELAYS_ENABLED` is False and every `/v1/relays` route answers 503, as federation does. Nothing in the tree calls them, node or client, and §11.1 measured two ISPs with no TURN relay needed. Kept code that nothing calls is what **L7** says not to keep; it stays only as the proof-of-possession design (**AV6**) until a node needs a relay or it is deleted |
+| **A very high bitrate wedges the player against a small buffer ceiling** | Where even the *floor* read-ahead does not fit — ninety seconds plus the minute kept behind, at the file's bitrate, above what the engine will hold — the film stalls: measured on the harness at 9.3 Mbit/s against a 100 MB ceiling, 100.8 s of film played in 900 s of wall clock. **Predates the byte budget and is unchanged by it**, to the tenth of a second; what the budget did change there is the refusal count, 1560 → 2. The fix is not a bound at all, it is a second stage of buffer outside the SourceBuffer, which means gating the append path — the riskiest change in this area and not one to make alongside another |
+| **A reconnection throws the read-ahead away** | `addReconnectListener` resumes with a seek to the playhead, and `reinitAt` empties the SourceBuffer, so a transport drop costs every second of buffer that was the point of building it. Harmless while the read-ahead was ninety seconds and the buffer was empty by the time the link returned; with a budget of minutes it is the thing that stops the budget paying. Resuming at the *end* of what is buffered instead is the fix, and it touches the seek path, which is why it is here and not in the same commit |
+| **The reconnect backoff only wakes on `visibilitychange`** | So a tab that stays visible through an outage — which is what a screen wake lock guarantees while a film is playing — waits out the full backoff, up to 30 s, after the network is already back. Nothing listens for `online` |
| **Per-device revocation has no CLI** | A device is revoked over MNP (`roster.revoke_device`), from a device the node has already pinned. On a headless node the operator's only lever is `member unpin`, which removes **every** device of that account — so the per-device control the roster is built around is reachable from an interface and from nowhere else. §6.7 listed a `meshbay-node member device list\|revoke` verb that was never written, and that listing is how this was found: `USERGUIDE.md` was the first document written by reading the CLI rather than this specification, and the verb it copied out did not run |
| **Migrations run on SQLite only** | The chain reaches head and agrees with the models there (§12), which is not where it ships. **The exposure is one revision deep, not the whole chain**: every revision behind the first packaged release was development that no installation ever ran, so nothing replays them on PostgreSQL. What is unguarded is the *next* migration — a default, an index type or a constraint PostgreSQL refuses reaches a deploy without the suite saying so |
| **A node key is read from the terminal or the desktop client, never a browser** | **Accepted.** `meshbay-node status` on the node's own machine and Node → Overview in the desktop client are the two places the key can be read; the Node page is Electron-only, because `platform.node` resolves to "not available" without the bridge, and no hub route exposes the key. The create-group wizard links it automatically over that same bridge, so the manual paste in **Profile → Link Node** exists for the operator who runs the node from a terminal and the hub from a browser — who has a terminal by definition. Anyone linking a node is already at a shell prompt, so a browser-reachable copy would buy nothing and widen what the hub knows about the node |