From 568370b7c215f83960f0ec7184234bdf2f5116ad Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Mon, 15 Jun 2026 16:24:13 +0200 Subject: [PATCH] =?UTF-8?q?fix(client):=20space=20proof=20moves=20past=20t?= =?UTF-8?q?he=20stance=20cooldown=20=E2=80=94=20test=5Fproof=5Fcorner=5Fre?= =?UTF-8?q?veal=20flake=20(T-1037)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit test_proof_corner_reveal drove moves back-to-back. The Gauntlet player is Walk stance (ticks_per_move=2, server stance.rs), so a move arriving within the cooldown window is silently throttled (movement.rs::apply_move -> PlayerMoveCooldown::try_move). _send_and_receive returned the instant a move landed, so the next send raced the cooldown and an occasional move was dropped — leaving the player one tile short, which (via the test's cumulative expectations) cascaded into the corner-reveal perception assert. A different move dropped each run = flaky; exposed by the heavier post-cascade tick timing. Test-side fix only — game behaviour is correct and the Gauntlet world is immutable: after a move lands, wait for the server clock to advance COOLDOWN_TICKS past the landing tick before the next send. Full client suite 2538/2538 green; the proof suite drops from ~28s (timeouts) to ~4s. Co-Authored-By: Claude Opus 4.8 (1M context) --- client/tests/test_sprint2_proof.gd | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/client/tests/test_sprint2_proof.gd b/client/tests/test_sprint2_proof.gd index 19dc96834..a3bc57302 100644 --- a/client/tests/test_sprint2_proof.gd +++ b/client/tests/test_sprint2_proof.gd @@ -16,6 +16,12 @@ extends GdUnitTestSuite const CONNECT_TIMEOUT: float = 3.0 const RESPONSE_TIMEOUT: float = 5.0 +# Server move cooldown: the Gauntlet player is Walk stance (ticks_per_move = 2, +# server stance.rs). A move input arriving within that window is silently +# throttled away (movement.rs::apply_move -> PlayerMoveCooldown::try_move). +# Space consecutive sends past it (2 ticks + 1 margin for apply/snapshot lag) +# so back-to-back moves can never race the cooldown. +const COOLDOWN_TICKS: int = 3 const MAX_PORT_ATTEMPTS: int = 5 var _server_pid: int = -1 @@ -128,6 +134,27 @@ func _send_and_receive( assert_that(actual.is_equal_approx(expect_position)).override_failure_message( "player must reach %s after '%s' — last seen %s" % [expect_position, action_name, actual] ).is_true() + # Space the next move past the per-stance cooldown. The server silently + # drops a move that arrives within ticks_per_move ticks of the previous + # one; without this wait, back-to-back sends race the cooldown and an + # occasional move is throttled, leaving the player one tile short (flaky). + # Wait for the server clock to advance COOLDOWN_TICKS past the landing + # tick so the next send can never be throttled. The player is stationary + # meanwhile, so the fresher snapshot is still valid for the caller. + var landed_tick: int = int(snapshot.tick) + var spacing_elapsed := 0.0 + while spacing_elapsed < RESPONSE_TIMEOUT: + _bridge.poll() + var m := _bridge.poll_message() + if m.size() > 0: + var d: Variant = Protocol.decode_snapshot(m) + if d != null: + snapshot = d + if int(d.tick) >= landed_tick + COOLDOWN_TICKS: + break + continue + await get_tree().create_timer(0.05).timeout + spacing_elapsed += 0.05 return snapshot