fix(protocol): deliver self-initiated state changes to the actor too
A connected Windows client would randomly snap from its joined channel back to Lobby. Root cause was a state-sync inconsistency, not a drop: the server delivered self-initiated state changes (channel join/leave, stream announce/stop) only as a private *Result to the actor and broadcast the authoritative UserEvent::UPDATED to everyone else. The core never applied the result to its SessionModel, so vc_list_users() kept self in the old channel; the Windows HandleUserUpdated rebuilds _currentChannelId from vc_list_users() on any user's UPDATED event, so the next unrelated event surfaced the stale self-channel. Fix, per the response-vs-broadcast contract now documented in docs/protocol.md §6: the *Result is pure ack/correlation/actor-private payload; the resulting state change is broadcast to every client INCLUDING the actor, and clients apply it to their local model rather than re-deriving own state from a *Result. - server: join/leave/stream announce+stop broadcast with exclude=0 - server: text fan-out includes the sender (channel + private echo) - core: response handlers no longer mutate session_model_ - windows: drop optimistic text echo; render own message via the relay - docs/protocol.md §6: document the response-vs-broadcast contract Registry-level admin broadcasts (move/mute/kick/channel CRUD) already used exclude=0 and were correct. ctest build/m1-dev 18/18 green; VoiceCat.App builds 0 warnings. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -274,6 +274,14 @@ message TextMessage {
|
||||
user, kick, ban, server-mute, set-permission, create/reset/delete account). Error `code`s
|
||||
are an enumerated, stable list.
|
||||
- Fatal conditions send **`Disconnect { code; reason }`** then close the TLS connection.
|
||||
- **The response is for the request; the broadcast is for the state.** A `*Result` only
|
||||
acknowledges the actor's request (correlation via `request_id`, error text, and any
|
||||
actor-private payload — e.g. the channel `AudioConfig` in `JoinChannelResult`). The
|
||||
resulting *state change* is delivered to **every** connected client **including the actor**
|
||||
via the normal `UserEvent` / `ChannelEvent` / relayed `TextMessage` path. Clients apply
|
||||
those events to their local model and never re-derive their own state from a `*Result`
|
||||
(doing so drifts: the actor would miss its own change and a later event for another user
|
||||
would surface the stale value).
|
||||
|
||||
## 7. Keepalive & timeouts
|
||||
|
||||
|
||||
Reference in New Issue
Block a user