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.