Probe: survivor-side disconnect feedback during opponent grace window #122
Labels
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/cellblock#122
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Behavior (operator playtest 2026-06-23 post round-22)
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:
render.ts drawDisconnectBannerwas claimed to handle the survivor side (which is why #93 was scoped leaver-only).Probe-first (substrate-grounded investigation)
drawDisconnectBannerexists at render.ts and is invoked when opponent-disconnect-event firesPossible findings (per probe)
Acceptance criteria (refine per probe)
Cross-refs
Anchor
2026-06-23 operator playtest. Empirical observation contradicting the survivor-already-handled scope claim from #93 design — worth probe before assuming gap-exists.
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 wheneverstate.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 theopponentDisconnectserver message (net.ts:205) and cleared byopponentReconnect(net.ts:209). The survivor IS wired to be notified.Server emit — present:
handleMatchDisconnect(reconnect.go:118, called fromLeave, lobby.go:212) pauses the match and sendsopponentDisconnectto the survivor (reconnect.go:143) withGraceSeconds, 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
writePumpdoes not tear downreadPump(writePump justreturns on write-error, main.go:243; readPump stays blocked onReadMessageuntil 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:opponentDisconnect(the server doesn't know yet) → no banner → the survivor's view of the opponent looks frozen/unresponsive.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):
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).