Fix pivot tap-spam speed exploit (#72)
_finish_pivot_tap() granted a full PIVOT_MIN_STEP rotation on every button/key release with no rate limit, so tapping fast enough rotated the formation far beyond PIVOT_SPEED's smooth held-rate cap - well above what holding the button down can ever produce. Each of those artificially-frequent re-seats (Fleet.apply_formation()) forced every ship into a fresh max-speed catch-up dash toward its new slot with no time to settle between dashes, and since outer-wedge ships swing further per pivot than inner ones, spamming made them visibly diverge in speed while each ship's own bounded max_speed made the fleet read as constantly pinned at its speed ceiling. Added a per-direction cooldown (the time a genuine hold needs to reach PIVOT_MIN_STEP on its own) so tap-spam can't out-pace holding the button. Deliberately NOT shared between CW/CCW: a quick correction tap the other way right after an overshoot is normal play, and test_keyboard_input.gd already covers Q and E each independently producing their own minimum step back-to-back - only repeatedly re-tapping the SAME direction needed capping.
This commit is contained in:
parent
1de66c6fb2
commit
d68aac6444
2 changed files with 46 additions and 3 deletions
|
|
@ -42,8 +42,28 @@ var pinch_start_zoom: float = 1.0
|
|||
# release, so tapping still produces a clear, visible nudge.
|
||||
const PIVOT_SPEED: float = 1.6
|
||||
const PIVOT_MIN_STEP: float = PI / 10.0
|
||||
# #72: the tap top-up below used to fire in full on EVERY release, with no
|
||||
# regard for how recently the last one landed - so tapping fast enough
|
||||
# granted rotation far above PIVOT_SPEED's smooth rate, well beyond what
|
||||
# holding the button can ever produce. Each of those artificially-frequent
|
||||
# re-seats (Fleet.apply_formation()) forces every ship into a fresh
|
||||
# max-speed catch-up dash toward its new slot with no time to settle
|
||||
# between them, and since outer-wedge ships swing further per pivot than
|
||||
# inner ones, spamming made them visibly diverge in speed ("desync") while
|
||||
# each individual ship's own bounded max_speed made the fleet as a whole
|
||||
# read as constantly at its speed ceiling ("build up speed"). This cooldown
|
||||
# is exactly the time a genuine hold needs to reach PIVOT_MIN_STEP on its
|
||||
# own, so tap-spam can't out-pace holding the button down.
|
||||
const PIVOT_TAP_COOLDOWN: float = PIVOT_MIN_STEP / PIVOT_SPEED
|
||||
|
||||
var pivot_hold_amount: float = 0.0
|
||||
# Tracked PER DIRECTION, not shared - a single quick correction (tap the
|
||||
# other way right after an overshoot) is normal play, not the spam this
|
||||
# cooldown targets, and Q/E each independently producing their own minimum
|
||||
# step back-to-back is an existing, tested guarantee (test_keyboard_input.gd).
|
||||
# Only repeatedly re-tapping the SAME direction needs to be capped.
|
||||
var pivot_cw_tap_cooldown_left: float = 0.0
|
||||
var pivot_ccw_tap_cooldown_left: float = 0.0
|
||||
# Touch index currently holding each pivot button, or -1. These buttons are
|
||||
# driven straight from raw touch events rather than through _gui_input,
|
||||
# because Godot's Control GUI treats touch as a single emulated pointer and
|
||||
|
|
@ -186,9 +206,16 @@ func _update_pivot(delta: float) -> void:
|
|||
|
||||
func _finish_pivot_tap(direction: float) -> void:
|
||||
# Fired on release. A press held long enough has already turned far
|
||||
# enough on its own; a brief tap gets topped up to the minimum step.
|
||||
if pivot_hold_amount < PIVOT_MIN_STEP:
|
||||
# enough on its own; a brief tap gets topped up to the minimum step -
|
||||
# but only once THAT DIRECTION's own cooldown has cleared, so spamming
|
||||
# one side can't grant rotation faster than holding the button would.
|
||||
var cooldown_left: float = pivot_cw_tap_cooldown_left if direction > 0.0 else pivot_ccw_tap_cooldown_left
|
||||
if pivot_hold_amount < PIVOT_MIN_STEP and cooldown_left <= 0.0:
|
||||
_pivot((PIVOT_MIN_STEP - pivot_hold_amount) * direction)
|
||||
if direction > 0.0:
|
||||
pivot_cw_tap_cooldown_left = PIVOT_TAP_COOLDOWN
|
||||
else:
|
||||
pivot_ccw_tap_cooldown_left = PIVOT_TAP_COOLDOWN
|
||||
pivot_hold_amount = 0.0
|
||||
|
||||
|
||||
|
|
@ -211,6 +238,8 @@ func _on_joystick_direction(direction: Vector2) -> void:
|
|||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
pivot_cw_tap_cooldown_left = maxf(pivot_cw_tap_cooldown_left - delta, 0.0)
|
||||
pivot_ccw_tap_cooldown_left = maxf(pivot_ccw_tap_cooldown_left - delta, 0.0)
|
||||
_update_pivot(delta)
|
||||
|
||||
# Keep the heading fresh while a control is held. Fleet advances its own
|
||||
|
|
|
|||
|
|
@ -5,7 +5,10 @@ extends SceneTree
|
|||
# Covers the fleet-pivot buttons:
|
||||
# 1. HOLDING one turns the formation continuously, not once per press
|
||||
# 2. a brief tap still produces a clear minimum step
|
||||
# 3. pivoting works while the fleet is under way, i.e. the new layout
|
||||
# 3. immediately repeating a tap grants nothing extra until the cooldown
|
||||
# clears (#72) - spamming taps must never rotate faster than holding
|
||||
# the button would
|
||||
# 4. pivoting works while the fleet is under way, i.e. the new layout
|
||||
# survives the joystick's per-frame steering updates instead of being
|
||||
# overwritten by the old offsets
|
||||
|
||||
|
|
@ -76,6 +79,17 @@ func _process(delta: float) -> bool:
|
|||
quit(1)
|
||||
return true
|
||||
|
||||
# --- #72: spamming taps grants nothing extra during the cooldown ---
|
||||
var before_spam: float = _angle()
|
||||
for i in range(5):
|
||||
controller._finish_pivot_tap(1.0)
|
||||
var spammed: float = absf(angle_difference(before_spam, _angle()))
|
||||
print("5 taps back-to-back -> turned %.4f rad (expected ~0, cooldown not yet cleared)" % spammed)
|
||||
if spammed > 0.001:
|
||||
print("TEST_PIVOT_CONTROLS: FAIL (tap-spam granted rotation during the cooldown)")
|
||||
quit(1)
|
||||
return true
|
||||
|
||||
# --- pivot WHILE the joystick is held -------------------------
|
||||
# Two fingers at once. This is routed through the same raw-touch
|
||||
# path the real device uses, because Godot's Control GUI treats
|
||||
|
|
|
|||
Loading…
Reference in a new issue