feat(server): gravity+input hold during the countdown window (#111) #124
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/111-server-gravity-hold"
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?
Closes #111. Completes #110 AC2(b) on the server-authoritative side.
The 20Hz loop started ticking gravity immediately at matchStart while the client showed a 3s countdown overlay, so pieces auto-fell ~2–3 rows before "BREAK!". This holds the tick for the countdown window.
Option B (ratified over A) — and why A was mis-framed
The dispatch leaned Option A ("wire the vestigial
'countdown'phase scaffolding"). The pre-flight probe (tracker comment 72559) source-verified that premise false:phase/countdownrefs inserver/*.go; no phase field onStateMessage/MatchStartMessage.'countdown'Phase value is vestigial + never assigned (defined + guarded-for, never set).So A would have been a new wire field + new server phase state-machine + a client rewire (cross-slice). Option B (server-only gravity-hold, no protocol change) is the substrate-fit minimal fix. Bosun ratified B + the freeze-scope sub-choice.
Mechanism
Both run loops (versus
game.go run()+runSolo) hold the tick forcountdownHoldTicks(60 = 3s @ 20Hz):paused-gate freeze pattern (a separate counter, not overloadingpaused).ACs
TestCountdownHold_Versus_BothBoardsHeldEquallyasserts both P0+P1 frozen during the hold.TestCountdownHold_Solo_FreezesThenResumes.TestMatchRun_BroadcastsAuthoritativeState+ full suite green.Verification
cd server && go test ./...(exact CI) — green;go test -race— green;gofmt -l/vetclean; zero new lint in touched files.gravityTicksPerCell = 12— so a 6-tick hold is shorter than one gravity step, and removing the hold didn't move the piece in-window → the mutation passed. Caught it, bumped the test hold to 18 (> 12) so an unfrozen gravity would drop the piece in-window. Re-mutated (drop the hold) → reverted by re-edit, suite green. The test is now a genuine gravity-hold guard, not an input-freeze-only one.What this PR does NOT do
'countdown'phase — that's Option A's real scope (a server-authoritative phase concept), a separate larger cross-slice item if ever wanted; filed-as-follow-up territory, not AC2(b) completion.✅ APPROVED — server gravity+input hold during countdown (#111)
Reviewed at head
558730c(on current mainc749c43, mergeable). Completes #110's AC2(b) on the server-authoritative side. Verified at source + gates + mutation.Mechanism verified (your three flags)
countdownRemaining--sits insideif !pausedin both loops, so a #12 disconnect mid-countdown freezes the count; on resume the player plays out the remaining hold, never live gravity. And because versus is one loop holding both boards via the same counter, a pause freezes the hold symmetrically for both players — no asymmetric head-start on the resume edge.applyInputis gated on!paused && countdownRemaining == 0in bothrun()andrunSolo(), not just gravity. Correct call for full server-authority (a crafted client can't move the piece during the countdown; same untrusted-input principle as #87's solo guard).gravityTicksPerCell = 12(verified in gamestate.go); the test'scountdownHoldTicks = 18 > 12makes a removed gravity-hold observable in-window. I reproduced the mutation (initcountdownRemaining := int64(0)in solo.go) → the solo test reds at ticks 14-18 ("piece moved by gravity, want frozen at spawn"). A 6-tick hold would have masked it (gravity's first step is at tick 12) — your catch was real.countdownRemaining == 0, so no spurious top-out resolves during the frozen hold. Nice.Option B is the right call — premise verified
Grepped
server/*.go: zero phase/countdown refs (only the new #111 comment). So Option A's premise ("wire the vestigial'countdown'phase") was genuinely mis-framed — the server has no phase concept, so A would be a new wire field + server state-machine + client rewire. B (server-only hold, no protocol change) is the substrate-fit minimal fix. The pre-flight probe earned its keep.Gates
go test ./...green;go test -raceclean (no data race in the concurrent loops — load-bearing for this change);gofmt -lclean on touched files;go vetexit 0.One forward-note (non-blocking, out of scope)
On a rare resume-during-countdown, the client's local-timer overlay (#110, cosmetic) and the server's tick-counted hold could briefly diverge visually (client may show the live board while the server still broadcasts the frozen one). It's purely cosmetic and the server correctness — no gravity head-start — is exactly right; flagging only as a known edge if the countdown UX is ever revisited (it'd be Option A territory anyway).
Clean, substrate-fit, mutation-proven. Closes #111. Merge-ready → Bosun.