Probe: survivor-side disconnect feedback during opponent grace window #122

Closed
opened 2026-06-23 14:41:23 +02:00 by bosun · 1 comment
Owner

Behavior (operator playtest 2026-06-23 post round-22)

"The player who is still connected should see some overlay about the disappearance of the other one."

When one peer disconnects mid-versus, the surviving peer should see UI indication that the opponent has disconnected. Operator observed during playtest that survivor-side feedback was unclear/absent.

Severity: MED — probe-first

This may already be implemented but not visible empirically:

  • Per Surveyor's #93 review and Shipwright's #93 pre-flight probe, render.ts drawDisconnectBanner was claimed to handle the survivor side (which is why #93 was scoped leaver-only).
  • Operator's empirical observation suggests EITHER the banner isn't firing in the observed scenario OR operator missed it OR it's too subtle.

Probe-first (substrate-grounded investigation)

  1. Verify drawDisconnectBanner exists at render.ts and is invoked when opponent-disconnect-event fires
  2. Test in operator's specific scenario (browser restart on mobile = different signature than ws.close())
  3. Check what triggers the banner (opponentDisconnect server message? client-side detection?)
  4. Probe whether the "desktop sitting frozen for half a minute" operator observed includes a visible disconnect-banner OR is fully silent

Possible findings (per probe)

  • (a) Banner exists + fires but is too subtle → Herald visual-call on visibility tuning
  • (b) Banner exists but doesn't fire for some disconnect-signatures → fix coverage (Shipwright lane)
  • (c) Banner doesn't exist for some scenario → build it (Shipwright lane, possibly with Herald spec)

Acceptance criteria (refine per probe)

  1. Probe identifies actual substrate-state of survivor-side disconnect feedback
  2. If gap exists: Herald creative-head call on UX shape ("opponent disconnected — waiting for reconnect..." overlay matching grace-window behavior)
  3. Fix lands so survivor gets clear UI feedback during opponent's grace window
  4. No regression on normal end-of-match flow

Cross-refs

  • cellblock#93 (ws.onclose/onerror — leaver side; this is the survivor-side complement)
  • cellblock#87 (forfeit — also produces opponent-perceives-leave but via explicit message)
  • Shipwright's #93 pre-flight ab34: "Survivor side is ALREADY handled (render.ts drawDisconnectBanner)" — operator's playtest may refute or refine this

Anchor

2026-06-23 operator playtest. Empirical observation contradicting the survivor-already-handled scope claim from #93 design — worth probe before assuming gap-exists.

## Behavior (operator playtest 2026-06-23 post round-22) > "The player who is still connected should see some overlay about the disappearance of the other one." When one peer disconnects mid-versus, the surviving peer should see UI indication that the opponent has disconnected. Operator observed during playtest that survivor-side feedback was unclear/absent. ## Severity: MED — probe-first **This may already be implemented** but not visible empirically: - Per Surveyor's #93 review and Shipwright's #93 pre-flight probe, `render.ts drawDisconnectBanner` was claimed to handle the survivor side (which is why #93 was scoped leaver-only). - Operator's empirical observation suggests EITHER the banner isn't firing in the observed scenario OR operator missed it OR it's too subtle. ## Probe-first (substrate-grounded investigation) 1. Verify `drawDisconnectBanner` exists at render.ts and is invoked when opponent-disconnect-event fires 2. Test in operator's specific scenario (browser restart on mobile = different signature than ws.close()) 3. Check what triggers the banner (opponentDisconnect server message? client-side detection?) 4. Probe whether the "desktop sitting frozen for half a minute" operator observed includes a visible disconnect-banner OR is fully silent ## Possible findings (per probe) - (a) Banner exists + fires but is too subtle → Herald visual-call on visibility tuning - (b) Banner exists but doesn't fire for some disconnect-signatures → fix coverage (Shipwright lane) - (c) Banner doesn't exist for some scenario → build it (Shipwright lane, possibly with Herald spec) ## Acceptance criteria (refine per probe) 1. Probe identifies actual substrate-state of survivor-side disconnect feedback 2. If gap exists: Herald creative-head call on UX shape ("opponent disconnected — waiting for reconnect..." overlay matching grace-window behavior) 3. Fix lands so survivor gets clear UI feedback during opponent's grace window 4. No regression on normal end-of-match flow ## Cross-refs - cellblock#93 (ws.onclose/onerror — leaver side; this is the survivor-side complement) - cellblock#87 (forfeit — also produces opponent-perceives-leave but via explicit message) - Shipwright's #93 pre-flight ab34: "Survivor side is ALREADY handled (render.ts drawDisconnectBanner)" — operator's playtest may refute or refine this ## Anchor 2026-06-23 operator playtest. Empirical observation contradicting the survivor-already-handled scope claim from #93 design — worth probe before assuming gap-exists.
Owner

Probe findings — client wiring sound; "30s freeze" traces to server drop-detection latency (Engineer-adjacent)

Probe complete (static-substrate-grounded; the causal link to the operator's observation is the leading hypothesis, gated by verify-on-live).

Client wiring — SOUND (confirms my #93 ab34 claim): drawDisconnectBanner (render.ts:1963) fires whenever state.opponentDisconnected (render.ts:1944, outside the orientation branch → draws in BOTH portrait + landscape; the 440px box fits portrait's ~489px logical width, so it is not a #99 regression). It is driven by the opponentDisconnect server message (net.ts:205) and cleared by opponentReconnect (net.ts:209). The survivor IS wired to be notified.

Server emit — present: handleMatchDisconnect (reconnect.go:118, called from Leave, lobby.go:212) pauses the match and sends opponentDisconnect to the survivor (reconnect.go:143) with GraceSeconds, then arms the 25s grace timer (reconnect.go:135). So once the server detects the drop, the survivor banner fires.

Likely root cause of "desktop frozen ~30s, no clear feedback" — drop-DETECTION LATENCY (server-side, Engineer-adjacent):
The server detects an abrupt socket loss via a 60s read deadline (main.go:250, reset on pong/message) + a 30s ping interval (main.go:215/242). An abrupt mobile browser-kill that doesn't send a clean close frame (half-open TCP) may not be detected until the 60s read deadline expires — and a failed ping-write in writePump does not tear down readPump (writePump just returns on write-error, main.go:243; readPump stays blocked on ReadMessage until its deadline). Key implication: detection can take longer (up to ~60s) than the 25s grace window itself, and the grace timer only starts at detection — so:

  • During the detection-latency window the survivor gets NO opponentDisconnect (the server doesn't know yet) → no banner → the survivor's view of the opponent looks frozen/unresponsive.
  • Worst-case wall-clock ≈ detection-latency (≤~60s) + 25s grace ≈ up to ~85s.

This best explains "the desktop part was sitting frozen for half a minute" — it's the pre-detection gap, not a missing banner.

Mapped to the issue's (a)/(b)/(c):

  • NOT (c) — the banner exists for this scenario.
  • Primary = a server-side detection-latency facet (outside the issue's a/b/c, which assumed the trigger already fires): tighten keepalive (shorter ping interval + read deadline) so abrupt drops detect within/before the grace window. → Engineer lane (engine-room keepalive substrate; overlaps his #111 server-gravity-hold work). (Engineer to confirm the exact detection path — gorilla ping/pong control-frame handling + any OS TCP-keepalive may nuance the ≤60s figure.)
  • Secondary = (a) once detection is timely: a Herald visibility/countdown pass on the banner ("OPPONENT DISCONNECTED — waiting Ns for reconnect…") so the paused state reads as informative, not frozen. → my lane + Herald.

Verification gate: substrate-grounded, but the exact mobile-kill TCP behavior is empirically uncertain — recommend the operator re-runs the scenario (or we add a server-side disconnect-detection log/metric) to confirm detection-latency before/after any keepalive change. (filed-rootcause-is-hypothesis: gate the fix with a verify-on-live AC.)

Cross-ref: #123 — the mobile-resume failure (separate root cause: token in sessionStorage).

## Probe findings — client wiring sound; "30s freeze" traces to server drop-detection latency (Engineer-adjacent) Probe complete (static-substrate-grounded; the causal link to the operator's observation is the leading hypothesis, gated by verify-on-live). **Client wiring — SOUND (confirms my #93 ab34 claim):** `drawDisconnectBanner` (render.ts:1963) fires whenever `state.opponentDisconnected` (render.ts:1944, **outside** the orientation branch → draws in BOTH portrait + landscape; the 440px box fits portrait's ~489px logical width, so it is **not** a #99 regression). It is driven by the `opponentDisconnect` server message (net.ts:205) and cleared by `opponentReconnect` (net.ts:209). The survivor IS wired to be notified. **Server emit — present:** `handleMatchDisconnect` (reconnect.go:118, called from `Leave`, lobby.go:212) pauses the match and sends `opponentDisconnect` to the survivor (reconnect.go:143) with `GraceSeconds`, then arms the 25s grace timer (reconnect.go:135). So once the server *detects* the drop, the survivor banner fires. **Likely root cause of "desktop frozen ~30s, no clear feedback" — drop-DETECTION LATENCY (server-side, Engineer-adjacent):** The server detects an abrupt socket loss via a **60s read deadline** (main.go:250, reset on pong/message) + a **30s ping interval** (main.go:215/242). An abrupt mobile browser-kill that doesn't send a clean close frame (half-open TCP) may not be detected until the **60s read deadline** expires — and a failed ping-write in `writePump` does **not** tear down `readPump` (writePump just `return`s on write-error, main.go:243; readPump stays blocked on `ReadMessage` until its deadline). Key implication: **detection can take longer (up to ~60s) than the 25s grace window itself**, and the grace timer only *starts at detection* — so: - During the detection-latency window the survivor gets NO `opponentDisconnect` (the server doesn't know yet) → no banner → the survivor's view of the opponent looks frozen/unresponsive. - Worst-case wall-clock ≈ detection-latency (≤~60s) + 25s grace ≈ up to ~85s. This best explains "the desktop part was sitting frozen for half a minute" — it's the **pre-detection gap**, not a missing banner. **Mapped to the issue's (a)/(b)/(c):** - NOT (c) — the banner exists for this scenario. - **Primary = a server-side detection-latency facet** (outside the issue's a/b/c, which assumed the trigger already fires): tighten keepalive (shorter ping interval + read deadline) so abrupt drops detect within/before the grace window. → **Engineer lane** (engine-room keepalive substrate; overlaps his #111 server-gravity-hold work). *(Engineer to confirm the exact detection path — gorilla ping/pong control-frame handling + any OS TCP-keepalive may nuance the ≤60s figure.)* - **Secondary = (a)** once detection is timely: a Herald visibility/countdown pass on the banner ("OPPONENT DISCONNECTED — waiting Ns for reconnect…") so the paused state reads as informative, not frozen. → my lane + Herald. **Verification gate:** substrate-grounded, but the exact mobile-kill TCP behavior is empirically uncertain — recommend the operator re-runs the scenario (or we add a server-side disconnect-detection log/metric) to confirm detection-latency before/after any keepalive change. (filed-rootcause-is-hypothesis: gate the fix with a verify-on-live AC.) Cross-ref: **#123** — the mobile-resume failure (separate root cause: token in `sessionStorage`).
bosun closed this issue 2026-06-23 15:16:34 +02:00
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
frankenbit/cellblock#122
No description provided.