zebra-spaces: RemoteTileFSM — formalises frozen-thumb fix from f71e9e7
Third state machine. One instance per incoming screen/camera track,
keyed by kind+pubHex. Codifies the lifecycle:
inactive ──TRACK_ARRIVED──▶ receiving ──MUTED──▶ muted
▲ │
│ UNMUTED │ PRUNE / ENDED
└────────────────────┤
▼
removed
{receiving, muted} + ENDED → removed
* + LEFT → removed
MUTED is a debounce gate, not a deletion: UNMUTED within the runtime's
~1.5s window cancels the prune and stays receiving (transient network
blip). PRUNE fires from the runtime's setTimeout if still muted.
ENDED skips the debounce. LEFT (peer-left) wipes the tile from any
live state. TRACK_ARRIVED in receiving/muted swaps to the new stream
(publisher re-shared before our prune fired).
Removed is terminal — a re-share spins up a fresh FSM. entry into
removed nulls ctx.stream so the runtime can drop refs.
+ 15 unit tests covering happy path, debounce semantics, ENDED
short-circuit, LEFT from every state, re-share refresh, removed
terminality. test-fsm now reports 53 passed.
This commit is contained in:
parent
57013ec9ad
commit
2d75cd7547
2 changed files with 206 additions and 8 deletions
|
|
@ -905,6 +905,72 @@ const subscribeSpec = {
|
|||
},
|
||||
};
|
||||
|
||||
/* ==================================================================
|
||||
* RemoteTileFSM — one instance per incoming screen / camera track from
|
||||
* a remote publisher. Formalises the lifecycle that f71e9e7 patched
|
||||
* imperatively (frozen-thumb after unshare):
|
||||
*
|
||||
* inactive ──TRACK_ARRIVED──▶ receiving ──MUTED──▶ muted
|
||||
* ▲ │
|
||||
* │ UNMUTED │ PRUNE / ENDED
|
||||
* └────────────────────┤
|
||||
* ▼
|
||||
* removed
|
||||
*
|
||||
* {receiving, muted} + ENDED → removed
|
||||
* * + LEFT → removed (peer-left wipes the tile from every state)
|
||||
*
|
||||
* MUTED is a debounce gate, not a deletion: if UNMUTED arrives within
|
||||
* the runtime's debounce window (~1.5s) we stay receiving — the mute
|
||||
* was a transient network blip. If PRUNE fires (debounce expired and
|
||||
* still muted) the publisher really unshared and the tile dies.
|
||||
* ENDED skips the debounce. removed is terminal — a new tile gets
|
||||
* a fresh FSM. */
|
||||
const remoteTileSpec = {
|
||||
initial: 'inactive',
|
||||
context: { kind: '', pubHex: '', stream: null, lastError: null },
|
||||
states: {
|
||||
inactive: {
|
||||
on: {
|
||||
TRACK_ARRIVED: {
|
||||
target: 'receiving',
|
||||
action: (ctx, ev) => { if (ev.payload) ctx.stream = ev.payload.stream || ctx.stream; },
|
||||
},
|
||||
LEFT: 'removed',
|
||||
},
|
||||
},
|
||||
receiving: {
|
||||
on: {
|
||||
MUTED: 'muted',
|
||||
ENDED: 'removed',
|
||||
LEFT: 'removed',
|
||||
/* a fresh ontrack for the same pubHex+kind — publisher re-shared
|
||||
* before our prune fired; keep the new stream and stay receiving */
|
||||
TRACK_ARRIVED: {
|
||||
target: 'receiving',
|
||||
action: (ctx, ev) => { if (ev.payload && ev.payload.stream) ctx.stream = ev.payload.stream; },
|
||||
},
|
||||
},
|
||||
},
|
||||
muted: {
|
||||
on: {
|
||||
UNMUTED: 'receiving',
|
||||
PRUNE: 'removed',
|
||||
ENDED: 'removed',
|
||||
LEFT: 'removed',
|
||||
TRACK_ARRIVED: {
|
||||
target: 'receiving',
|
||||
action: (ctx, ev) => { if (ev.payload && ev.payload.stream) ctx.stream = ev.payload.stream; },
|
||||
},
|
||||
},
|
||||
},
|
||||
removed: {
|
||||
entry: (ctx) => { ctx.stream = null; },
|
||||
/* terminal — no outbound transitions */
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/* ==================================================================
|
||||
* identity — ed25519 keypair, persisted in localStorage as JWK.
|
||||
*
|
||||
|
|
@ -2973,8 +3039,8 @@ logLine('', 'ready — pick a handle, type a rendezvous code, enter the space');
|
|||
|
||||
<footer style="margin:2.2rem auto 0;font-size:0.65rem;color:#999;line-height:1.7;word-break:break-all;font-family:monospace">
|
||||
<span id="pi-seal" style="color:#777;cursor:default;user-select:none" title="">page integrity</span> · built <span class="stamp-date">2026-06-02</span><br>
|
||||
md5 <span class="stamp-md5">fabb116d48d04e00c0d689f14e475ead</span><br>
|
||||
sha256 <span class="stamp-sha">f449dab274cac4a6ecf36a7329eb759d1ea494789b054e1431408452168004aa</span><br>
|
||||
md5 <span class="stamp-md5">a3266c98f3e438ced8d92ede2203211b</span><br>
|
||||
sha256 <span class="stamp-sha">8b48fd39877e1a3a2d0dd155998489a09ca62f72eb0a59889ce9a0de4affe26a</span><br>
|
||||
<span style="color:#bbb">hashes are of this page with these two fields zeroed — to verify, blank them and re-hash</span><br>
|
||||
<span style="color:#bbb">one self-contained file — <strong>save a copy</strong> and verify against these hashes; point at your own servers with ?signal= and ?turncred=, or <a href="host-your-own.html" style="color:#999">host your own community</a></span>
|
||||
</footer>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue