test(client): pace input-roundtrip moves past the D-053 stance cooldown (T-1068 fix ported)
Root-caused with server trace logs: Walk stance throttles 1 move per 2 ticks and SILENTLY discards early moves (movement.rs apply_move -> try_move, TRACE-only) — under load the test's MoveEast landed exactly 1 tick after MoveNorth and was consumed. Server behavior is correct, deliberate D-053, pinned by Rust tests; production clients re-send while keys are held. Fix: COOLDOWN_TICKS=3 post-ack spacing, same as test_sprint2_proof.gd (T-1068) whose fix never reached this file due to helper copy-paste. Pre-fix repro at run 17/30 under load; post-fix 30/30 stress green + full suite 2956/2956. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,9 +9,21 @@ extends GdUnitTestSuite
|
||||
|
||||
const CONNECT_TIMEOUT: float = 3.0
|
||||
# Ceiling, not a sleep — the wait loop exits the moment the expected state
|
||||
# arrives. 5.0s flaked under load (gate run 2026-07-16: MoveEast observed
|
||||
# mid-move at timeout); 10.0s costs nothing when healthy.
|
||||
# arrives. NOTE: raising this can never fix a missing move. The 2026-07-16
|
||||
# gate flake ("MoveEast never registers") was the stance cooldown silently
|
||||
# discarding the input (see COOLDOWN_TICKS below), not slowness — a dropped
|
||||
# move stays dropped no matter how long the wait.
|
||||
const RESPONSE_TIMEOUT: float = 10.0
|
||||
# Server move cooldown: the proof-room 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 — TRACE-level log only, input consumed).
|
||||
# Space consecutive sends past it (2 ticks + 1 margin for apply/snapshot
|
||||
# lag) so back-to-back moves can never race the cooldown. Same fix as
|
||||
# test_sprint2_proof.gd (T-1068) — root-caused again in the T-180 hunt with
|
||||
# server trace logs: "Received input: tick=2 action=MoveEast" followed by
|
||||
# "Movement throttled by stance Walk cooldown".
|
||||
const COOLDOWN_TICKS: int = 3
|
||||
const MAX_PORT_ATTEMPTS: int = 5
|
||||
|
||||
var _server_pid: int = -1
|
||||
@@ -138,7 +150,9 @@ func _do_handshake(world_seed: int = 42) -> bool:
|
||||
## - expect_position != null (Vector2, render coords): wait until the player
|
||||
## reaches that position and assert it. Deterministic under load — tick
|
||||
## margins race the free-running server when the test process is descheduled
|
||||
## between drain and send.
|
||||
## between drain and send. Afterwards, hold until the server clock is
|
||||
## COOLDOWN_TICKS past the landing tick so the caller's next move can never
|
||||
## race the stance cooldown (which silently discards early move attempts).
|
||||
## - expect_position == null: wait for a snapshot at least 2 ticks past the
|
||||
## drain point (enough for no-op actions like Interact).
|
||||
func _send_and_receive(
|
||||
@@ -203,6 +217,25 @@ 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 (see COOLDOWN_TICKS).
|
||||
# The server silently drops a move arriving within ticks_per_move ticks
|
||||
# of the previous one; without this hold, back-to-back sends race the
|
||||
# cooldown and an occasional move is throttled, leaving the player one
|
||||
# tile short (the T-180 gate flake). The player is stationary meanwhile,
|
||||
# so the fresher snapshot is still valid for the caller.
|
||||
var landed_tick: int = int(snapshot.tick)
|
||||
var spacing_deadline_ms: int = Time.get_ticks_msec() + int(RESPONSE_TIMEOUT * 1000.0)
|
||||
while Time.get_ticks_msec() < spacing_deadline_ms:
|
||||
_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
|
||||
return snapshot
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user