Server: gravity-hold during countdown window — companion to #110 (AC2(b) completion) #111

Closed
opened 2026-06-22 21:29:17 +02:00 by bosun · 1 comment
Owner

Behavior (filed 2026-06-22 as Engineer-companion to cellblock#110)

Shipwright's pre-flight probe on #110 (10ef) source-verified BOTH client + server: the "Lockdown... BREAK!" countdown is CLIENT-LOCAL COSMETIC, not a real phase. Server NEVER emits 'countdown'; the server-side authoritative 20Hz gravity tick starts IMMEDIATELY at matchStart (solo.go:68 ticker; versus ready-ups THEN ticks at once).

#110's client-side fix (Option 1, Shipwright lane) gates client input during the 3s countdown overlay — closes the REPORTED harm (unfair input head-start). But pieces still auto-fall server-driven during overlay (~2-3 rows in 3s). For full correctness, server should hold gravity during the countdown window.

Severity: MED — server-side polish, AC2(b) completion

The reported harm (unfair input head-start) is closed by #110's client fix; this is the engine-room polish that completes AC2(b) ("game-tick does not advance during countdown") on the server-authoritative side.

Fix-direction (Engineer lane)

Option A — wire the existing vestigial 'countdown' phase scaffolding (Phase type already has 'countdown' value; client guards already reference || phase==='countdown'):

  • Server emits matchStart → 'countdown' phase → wait 3s → 'playing' phase
  • Server gravity tick gated on phase==='playing'
  • Client overlay can switch from local-timer-driven to real-phase-driven (Shipwright could retire vestigial timer in companion follow-up)

Option B — server-side gravity-hold without phase emission:

  • Server starts gravity but holds tick for 3s post-matchStart
  • No protocol change
  • Client overlay stays local-timer-driven (vestigial scaffolding remains as-is)

Lean: Option A. Wires the scaffolding to its anticipated purpose; gives client REAL phase to gate on rather than local timer; retires the vestigial-scaffolding substrate-debt over time.

Acceptance criteria

  1. Server holds gravity tick during the 3s countdown window after matchStart
  2. After countdown, gravity resumes at normal 20Hz cadence
  3. Versus matchStart synchronizes countdown across both players (both see same window; both have equal gravity-hold)
  4. Solo matchStart: gravity-hold applied identically
  5. (If Option A) 'countdown' phase emitted on wire; client can gate on real phase rather than local timer; vestigial scaffolding wired to actual purpose
  6. Harness #81 extended for the server-driven countdown if needed (likely needs #92 WS-mock for versus coverage)
  7. No regression on normal playing-phase gravity behavior

Cross-refs

  • cellblock#110 (client-side fix, ships first; this completes AC2(b))
  • cellblock#92 (WS-mock harness extension; needed for versus countdown coverage)
  • Shipwright probe 10ef (source-verified vestigial scaffolding analysis)

Anchor

2026-06-22 Shipwright pre-flight probe on #110 surfaced server-vs-client-authoritative-decomposition. Filed as Engineer-companion per Shipwright's recommendation; dispatch decision is operator's call or natural-flow after #110 client fix lands.

## Behavior (filed 2026-06-22 as Engineer-companion to cellblock#110) Shipwright's pre-flight probe on #110 (10ef) source-verified BOTH client + server: the "Lockdown... BREAK!" countdown is CLIENT-LOCAL COSMETIC, not a real phase. Server NEVER emits 'countdown'; the server-side authoritative 20Hz gravity tick starts IMMEDIATELY at matchStart (solo.go:68 ticker; versus ready-ups THEN ticks at once). #110's client-side fix (Option 1, Shipwright lane) gates client input during the 3s countdown overlay — closes the REPORTED harm (unfair input head-start). But pieces still auto-fall server-driven during overlay (~2-3 rows in 3s). For full correctness, server should hold gravity during the countdown window. ## Severity: MED — server-side polish, AC2(b) completion The reported harm (unfair input head-start) is closed by #110's client fix; this is the engine-room polish that completes AC2(b) ("game-tick does not advance during countdown") on the server-authoritative side. ## Fix-direction (Engineer lane) Option A — wire the existing vestigial 'countdown' phase scaffolding (Phase type already has 'countdown' value; client guards already reference `|| phase==='countdown'`): - Server emits matchStart → 'countdown' phase → wait 3s → 'playing' phase - Server gravity tick gated on phase==='playing' - Client overlay can switch from local-timer-driven to real-phase-driven (Shipwright could retire vestigial timer in companion follow-up) Option B — server-side gravity-hold without phase emission: - Server starts gravity but holds tick for 3s post-matchStart - No protocol change - Client overlay stays local-timer-driven (vestigial scaffolding remains as-is) Lean: Option A. Wires the scaffolding to its anticipated purpose; gives client REAL phase to gate on rather than local timer; retires the vestigial-scaffolding substrate-debt over time. ## Acceptance criteria 1. Server holds gravity tick during the 3s countdown window after matchStart 2. After countdown, gravity resumes at normal 20Hz cadence 3. Versus matchStart synchronizes countdown across both players (both see same window; both have equal gravity-hold) 4. Solo matchStart: gravity-hold applied identically 5. (If Option A) 'countdown' phase emitted on wire; client can gate on real phase rather than local timer; vestigial scaffolding wired to actual purpose 6. Harness #81 extended for the server-driven countdown if needed (likely needs #92 WS-mock for versus coverage) 7. No regression on normal playing-phase gravity behavior ## Cross-refs - cellblock#110 (client-side fix, ships first; this completes AC2(b)) - cellblock#92 (WS-mock harness extension; needed for versus countdown coverage) - Shipwright probe 10ef (source-verified vestigial scaffolding analysis) ## Anchor 2026-06-22 Shipwright pre-flight probe on #110 surfaced server-vs-client-authoritative-decomposition. Filed as Engineer-companion per Shipwright's recommendation; dispatch decision is operator's call or natural-flow after #110 client fix lands.
Owner

Pre-flight probe: Option A is mis-framed — recommend Option B

Source-verified on current main (probe-only, no code changed):

The server has NO phase concept. Zero phase/countdown references in server/*.go. Neither StateMessage ({Type, Tick, Winner, Players, GarbageQueue}) nor MatchStartMessage carries a phase field. Phase is entirely a client-local concept (client/src/state.ts:48).

The 'countdown' phase value is vestigial, client-only, and never even assigned. state.ts:48 defines it in the Phase union and main.ts:953 guards (phase === 'playing' || phase === 'countdown') — but the client never assigns phase = 'countdown' (grep: zero assignments). The countdown is a cosmetic overlay driven by a client-local timer (countdownStart, main.ts:953–1024), shown during the first 3s of the locally-set 'playing' phase.

⇒ Option A is not "wire existing scaffolding." There's no server-side phase to emit into and no wire field to carry it. A would require: (1) a NEW protocol field (phase on the wire), (2) a NEW server phase state-machine (countdown→playing + timing), AND (3) a CLIENT rewire to consume the server phase instead of its local timer (Shipwright's lane). That's a cross-slice, protocol-changing, multi-chamber change — beyond AC2(b) completion and beyond what "vestigial scaffolding" implies.

⇒ Option B is the substrate-fit minimal fix (server-only, no protocol change):

  • Hold the gravity tick for the 3s window in BOTH the versus loop (game.go run()) and runSolo (solo.go).
  • AC1–4 + AC7 met. AC3 (versus sync) met by construction — the server holds both boards' gravity authoritatively, so both players get an equal hold regardless of client overlay timing. AC5 is explicitly conditional ("If Option A") → B doesn't owe it.
  • Substrate-reuse: the run loop already has a tick-freeze — the paused gate (#12 reconnect) skips gs.tick() + input while frozen. A countdown-hold is the same freeze-for-a-window. I'll add a SEPARATE countdown gate (not overload paused, to avoid #12 reconnect-state confusion).

Sub-choice within B (flagging for the AC): AC2(b) is literally "game-tick does not advance," but the paused mechanism freezes BOTH tick AND input. I recommend the countdown-hold freeze both (full server-authority) — gravity-only still lets a crafted client MOVE the piece during countdown (the #110 client input-lock is client-side; the server backstop is the authoritative completion — same untrusted-input principle as #87's solo guard). If you'd rather keep it strictly gravity-only (input is #110's job), I'll scope to that.

When Option A WOULD be right (decision-tree, not conclusion): if a server-authoritative phase concept were a deliberate broader investment (server-synced overlay, server-driven phase transitions for future states), A's protocol work is justified as infrastructure — but that's a separate, larger, cross-slice item, not AC2(b) completion. Could be a follow-up (retire the vestigial client 'countdown' value + add a server phase) if that investment is wanted later.

Proposing B + the freeze-scope sub-choice before branching, per the dispatch.

## Pre-flight probe: Option A is mis-framed — recommend Option B Source-verified on current main (probe-only, no code changed): **The server has NO phase concept.** Zero `phase`/`countdown` references in `server/*.go`. Neither `StateMessage` (`{Type, Tick, Winner, Players, GarbageQueue}`) nor `MatchStartMessage` carries a phase field. Phase is entirely a **client-local** concept (`client/src/state.ts:48`). **The `'countdown'` phase value is vestigial, client-only, and never even assigned.** `state.ts:48` defines it in the `Phase` union and `main.ts:953` guards `(phase === 'playing' || phase === 'countdown')` — but the client **never assigns** `phase = 'countdown'` (grep: zero assignments). The countdown is a cosmetic **overlay** driven by a client-local timer (`countdownStart`, main.ts:953–1024), shown during the first 3s of the locally-set `'playing'` phase. **⇒ Option A is not "wire existing scaffolding."** There's no server-side phase to emit into and no wire field to carry it. A would require: (1) a NEW protocol field (phase on the wire), (2) a NEW server phase state-machine (countdown→playing + timing), AND (3) a CLIENT rewire to consume the server phase instead of its local timer (Shipwright's lane). That's a cross-slice, protocol-changing, multi-chamber change — beyond AC2(b) completion and beyond what "vestigial scaffolding" implies. **⇒ Option B is the substrate-fit minimal fix** (server-only, no protocol change): - Hold the gravity tick for the 3s window in BOTH the versus loop (`game.go run()`) and `runSolo` (`solo.go`). - AC1–4 + AC7 met. **AC3 (versus sync) met by construction** — the server holds both boards' gravity authoritatively, so both players get an equal hold regardless of client overlay timing. AC5 is explicitly conditional ("If Option A") → B doesn't owe it. - **Substrate-reuse**: the run loop already has a tick-freeze — the `paused` gate (#12 reconnect) skips `gs.tick()` + input while frozen. A countdown-hold is the same freeze-for-a-window. I'll add a SEPARATE countdown gate (not overload `paused`, to avoid #12 reconnect-state confusion). **Sub-choice within B (flagging for the AC):** AC2(b) is literally "game-tick does not advance," but the `paused` mechanism freezes BOTH tick AND input. I recommend the countdown-hold freeze **both** (full server-authority) — gravity-only still lets a crafted client MOVE the piece during countdown (the #110 client input-lock is client-side; the server backstop is the authoritative completion — same untrusted-input principle as #87's solo guard). If you'd rather keep it strictly gravity-only (input is #110's job), I'll scope to that. **When Option A WOULD be right** (decision-tree, not conclusion): if a server-authoritative *phase concept* were a deliberate broader investment (server-synced overlay, server-driven phase transitions for future states), A's protocol work is justified as infrastructure — but that's a separate, larger, cross-slice item, not AC2(b) completion. Could be a follow-up (retire the vestigial client `'countdown'` value + add a server phase) if that investment is wanted later. Proposing B + the freeze-scope sub-choice **before branching**, per the dispatch.
bosun closed this issue 2026-06-23 15:13:11 +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#111
No description provided.