Client reconnect-via-resume on accidental drop (#93 follow-up — the seamless-resume half) #115

Closed
opened 2026-06-22 23:16:55 +02:00 by shipwright · 0 comments
Owner

Background

Filed as the deferred half of cellblock#93's fix-shape fork. #93 (PR landing now) ships Option A: ws.onclose/onerror → accidental-drop detection → brief "connection lost" overlay → backToYard(). That removes the frozen-client symptom under every drop scenario (the empirically-confirmed operator gap). This tracker is Option B: attempt a seamless reconnect before falling back to that return-to-title.

Operator ratified A-now + B-follow-up on #93 ("(A) per Shipwright's lean, if it won't mean too much wasted work; otherwise (B) straight"); the implementer wasted-work assessment came back LOW, so A shipped first.

Scope (the reconnect path — #93 AC3)

On an accidental drop while a resume token exists, attempt to re-establish the session before giving up:

  1. On handleDrop (accidental close, in net.ts), if a resume is viable, open a fresh WebSocket to the same /ws and let the existing onopen{type:'resume', token} path run (that machinery already exists in net.ts).
  2. If the resume succeeds (server replies matchStart/state), the client re-syncs and play continues — no return-to-title.
  3. If the resume fails (server error, socket won't open, or the grace window elapses), fall back to Option A's backToYard() recovery.

Verified substrate facts (from the #93 pre-flight)

  • Server grace window = 25s (server/reconnect.go:26 resumeGrace = 25 * time.Second). The server holds the match slot for 25s awaiting a resume — the reconnect attempt must complete inside that window. No server change needed; the resume path already exists.
  • The resume token is stored on matchStart (net.ts, LS_TOKEN); the client already sends {type:'resume', token} on a fresh socket's onopen.

Cost / why deferred

Option B requires a real net.ts restructure: the socket is currently a single const ws, so reconnect means making it reassignable and extracting handler-binding into a re-bindable function, plus attempt-accounting (retry count / window). This is net-new structure beyond A — A's handler registration, the deliberate/closed discrimination, the overlay, and backToYard() as the terminal fallback all transfer cleanly into B.

Acceptance criteria

  1. On an accidental drop with a live resume token, the client attempts reconnect-via-resume within the 25s grace window before falling back to backToYard().
  2. Successful resume re-syncs the client into the live match (no return-to-title).
  3. Failed/timed-out resume falls back to the Option-A return-to-title (no regression on #93's behaviour).
  4. Deliberate close() still skips reconnect entirely (the existing deliberate flag).
  5. Harness coverage via the #92 WS-mock: a srv.close() followed by a re-accept + matchStart proves the seamless-resume path; a srv.close() with no re-accept proves the fallback.
  6. No regression on the normal end-of-match or deliberate-teardown flows.

Cross-refs

  • cellblock#93 (Option A — the robustness core this builds on)
  • cellblock#92 (WS-mock substrate — srv.close() + re-accept drives both paths)

Anchor

2026-06-22 #93 pre-flight fork (Shipwright). Option A shipped; this is the deferred seamless-resume enrichment.

## Background Filed as the deferred half of cellblock#93's fix-shape fork. #93 (PR landing now) ships **Option A**: `ws.onclose`/`onerror` → accidental-drop detection → brief "connection lost" overlay → `backToYard()`. That removes the frozen-client symptom under every drop scenario (the empirically-confirmed operator gap). This tracker is **Option B**: attempt a *seamless reconnect* before falling back to that return-to-title. Operator ratified A-now + B-follow-up on #93 ("(A) per Shipwright's lean, if it won't mean too much wasted work; otherwise (B) straight"); the implementer wasted-work assessment came back LOW, so A shipped first. ## Scope (the reconnect path — #93 AC3) On an accidental drop while a resume token exists, attempt to re-establish the session before giving up: 1. On `handleDrop` (accidental close, in net.ts), if a resume is viable, **open a fresh WebSocket** to the same `/ws` and let the existing `onopen`→`{type:'resume', token}` path run (that machinery already exists in net.ts). 2. If the resume **succeeds** (server replies `matchStart`/`state`), the client re-syncs and play continues — no return-to-title. 3. If the resume **fails** (server `error`, socket won't open, or the grace window elapses), fall back to Option A's `backToYard()` recovery. ### Verified substrate facts (from the #93 pre-flight) - **Server grace window = 25s** (`server/reconnect.go:26 resumeGrace = 25 * time.Second`). The server holds the match slot for 25s awaiting a resume — the reconnect attempt must complete inside that window. No server change needed; the resume path already exists. - The resume token is stored on `matchStart` (`net.ts`, `LS_TOKEN`); the client already sends `{type:'resume', token}` on a fresh socket's `onopen`. ### Cost / why deferred Option B requires a real `net.ts` restructure: the socket is currently a single `const ws`, so reconnect means making it reassignable and extracting handler-binding into a re-bindable function, plus attempt-accounting (retry count / window). This is net-new structure beyond A — A's handler registration, the `deliberate`/`closed` discrimination, the overlay, and `backToYard()` as the terminal fallback all transfer cleanly into B. ## Acceptance criteria 1. On an accidental drop with a live resume token, the client attempts reconnect-via-resume within the 25s grace window before falling back to `backToYard()`. 2. Successful resume re-syncs the client into the live match (no return-to-title). 3. Failed/timed-out resume falls back to the Option-A return-to-title (no regression on #93's behaviour). 4. Deliberate `close()` still skips reconnect entirely (the existing `deliberate` flag). 5. Harness coverage via the #92 WS-mock: a `srv.close()` followed by a re-accept + `matchStart` proves the seamless-resume path; a `srv.close()` with no re-accept proves the fallback. 6. No regression on the normal end-of-match or deliberate-teardown flows. ## Cross-refs - cellblock#93 (Option A — the robustness core this builds on) - cellblock#92 (WS-mock substrate — `srv.close()` + re-accept drives both paths) ## Anchor 2026-06-22 #93 pre-flight fork (Shipwright). Option A shipped; this is the deferred seamless-resume enrichment.
bosun closed this issue 2026-06-23 20:36:29 +02:00
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
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#115
No description provided.