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.
|
# release, so tapping still produces a clear, visible nudge.
|
||||||
const PIVOT_SPEED: float = 1.6
|
const PIVOT_SPEED: float = 1.6
|
||||||
const PIVOT_MIN_STEP: float = PI / 10.0
|
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
|
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
|
# Touch index currently holding each pivot button, or -1. These buttons are
|
||||||
# driven straight from raw touch events rather than through _gui_input,
|
# driven straight from raw touch events rather than through _gui_input,
|
||||||
# because Godot's Control GUI treats touch as a single emulated pointer and
|
# 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:
|
func _finish_pivot_tap(direction: float) -> void:
|
||||||
# Fired on release. A press held long enough has already turned far
|
# 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.
|
# enough on its own; a brief tap gets topped up to the minimum step -
|
||||||
if pivot_hold_amount < PIVOT_MIN_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)
|
_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
|
pivot_hold_amount = 0.0
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -211,6 +238,8 @@ func _on_joystick_direction(direction: Vector2) -> void:
|
||||||
|
|
||||||
|
|
||||||
func _process(delta: float) -> 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)
|
_update_pivot(delta)
|
||||||
|
|
||||||
# Keep the heading fresh while a control is held. Fleet advances its own
|
# Keep the heading fresh while a control is held. Fleet advances its own
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,10 @@ extends SceneTree
|
||||||
# Covers the fleet-pivot buttons:
|
# Covers the fleet-pivot buttons:
|
||||||
# 1. HOLDING one turns the formation continuously, not once per press
|
# 1. HOLDING one turns the formation continuously, not once per press
|
||||||
# 2. a brief tap still produces a clear minimum step
|
# 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
|
# survives the joystick's per-frame steering updates instead of being
|
||||||
# overwritten by the old offsets
|
# overwritten by the old offsets
|
||||||
|
|
||||||
|
|
@ -76,6 +79,17 @@ func _process(delta: float) -> bool:
|
||||||
quit(1)
|
quit(1)
|
||||||
return true
|
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 -------------------------
|
# --- pivot WHILE the joystick is held -------------------------
|
||||||
# Two fingers at once. This is routed through the same raw-touch
|
# Two fingers at once. This is routed through the same raw-touch
|
||||||
# path the real device uses, because Godot's Control GUI treats
|
# path the real device uses, because Godot's Control GUI treats
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue