fix(host-control-mode): audit findings — host dedup demotion, episode-lobby freeze, +cleanups

Findings recovered from the multi-agent audit (verification phase was cut off by a
usage limit; verified inline against the code):

HIGH:
- Host was demoted on peerId dedup (fast reconnect / second tab): the dedup path
  removes the old socket and the kicked socket's 'disconnect' both ran the
  host-leave fallback before the same peerId re-joined → room silently unlocked on
  every host network blip. Now skip the fallback while a join for that peerId is in
  flight (peerJoinLocks). Regression test added. (A long real disconnect still
  falls back — that's the deferred host-grace EC-10.)
- Episode auto-advance froze a gated guest: in host-only the guest's EPISODE_LOBBY
  is dropped server-side, but the guest still self-paused (PAUSE_FOR_LOBBY) waiting
  for readies that never came → 60s freeze. A host-only guest now skips creating a
  lobby (the host drives episode sync).

LOW / cleanup:
- GET_HCM_STRINGS could return a raw key name ("HCM_DIALOG_TITLE") if the locale
  dictionary failed to load (getMessage returns the key on miss) → omit it so
  content keeps its English fallback.
- hcmReset now also clears the snap-back cooldown + buffering grace, so a stale
  cooldown can't swallow the first snap-back after a room/host change.
- Popup play/pause labels reset on lock too (not just unlock), so a button can't
  freeze on "Playing…" when host-only activates mid-click.
- Fix a stale comment (heartbeat now carries 'desynced').

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
KoalaDev
2026-06-28 02:25:11 +02:00
parent 02c41776b8
commit dfa0a3d03e
5 changed files with 59 additions and 19 deletions
+7 -1
View File
@@ -228,7 +228,13 @@ function removePeerFromRoom(socketId, roomId, reason) {
// socket), fall back to 'everyone' so the room never gets stuck locked, and
// reassign host to the earliest remaining peer so the feature stays usable.
// (v1: immediate fallback, no grace period — see host-control-mode docs.)
if (!isPeerStillConnected && room.hostPeerId === peerId && room.peers.size > 0) {
// Skip while a join for this peerId is in flight (peerJoinLocks holds it):
// that's a reconnect / second tab where the same peerId is being re-added
// right after — covers both the explicit 'dedupe' removal AND the
// 'disconnect' the kicked old socket fires. Demoting there would silently
// unlock the room on every host network blip.
const peerRejoining = peerJoinLocks.has(peerId);
if (!peerRejoining && !isPeerStillConnected && room.hostPeerId === peerId && room.peers.size > 0) {
const nextPeerData = room.peerData.values().next().value;
room.hostPeerId = nextPeerData ? nextPeerData.peerId : null;
room.controlMode = CONTROL_MODES.EVERYONE;