spaceship/tests/test_pivot_controls.gd

167 lines
6.1 KiB
GDScript3
Raw Normal View History

extends SceneTree
# Headless gameplay test: run with
# godot --headless -s res://tests/test_pivot_controls.gd
# 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. 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
var scene: Node
var fleet: Node
var controller: Node
var cw: IconButton
var stage: int = 0
var elapsed: float = 0.0
var angle_at_hold_start: float = 0.0
var angle_after_hold: float = 0.0
# GameState (an autoload) is not available as a bare identifier from a
# script that extends SceneTree - fetch it via the tree root instead.
var game_state: Node
func _initialize() -> void:
game_state = root.get_node("GameState")
game_state.reset_formation()
scene = load("res://Main.tscn").instantiate()
root.add_child(scene)
fleet = scene.get_node("Fleet")
controller = scene
cw = scene.get_node("UI/PivotCWButton")
func _angle() -> float:
return game_state.formation[1].angle()
func _process(delta: float) -> bool:
elapsed += delta
match stage:
0:
scene.get_node("AsteroidField").set_process(false)
scene.get_node("EnemySpawner").set_process(false)
# Hold the button down without releasing it.
angle_at_hold_start = _angle()
cw.pressed_down = true
elapsed = 0.0
stage = 1
return false
1:
if elapsed < 0.6:
return false
angle_after_hold = _angle()
var turned: float = angle_difference(angle_at_hold_start, angle_after_hold)
# 0.6s at PIVOT_SPEED should be far beyond a single tap step.
var held_ok: bool = absf(turned) > controller.PIVOT_MIN_STEP * 1.5
print("held 0.6s -> turned %.3f rad (min step %.3f) held_ok=%s" % [
absf(turned), controller.PIVOT_MIN_STEP, held_ok
])
if not held_ok:
print("TEST_PIVOT_CONTROLS: FAIL (holding did not turn continuously)")
quit(1)
return true
cw.pressed_down = false
controller.pivot_hold_amount = 0.0
# --- brief tap ------------------------------------------------
var before_tap: float = _angle()
controller._finish_pivot_tap(1.0)
var tapped: float = absf(angle_difference(before_tap, _angle()))
var tap_ok: bool = tapped >= controller.PIVOT_MIN_STEP - 0.001
print("tap -> turned %.3f rad tap_ok=%s" % [tapped, tap_ok])
if not tap_ok:
print("TEST_PIVOT_CONTROLS: FAIL (tap did not produce the minimum step)")
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
# touch as one emulated pointer and would not deliver a second
# simultaneous finger to a button - the exact reason pivoting
# while steering used to do nothing.
var joy: TouchJoystick = scene.get_node("UI/Joystick")
var stick_down: InputEventScreenTouch = InputEventScreenTouch.new()
stick_down.index = 0
stick_down.pressed = true
stick_down.position = joy.global_position + joy.size / 2.0
controller._handle_touch(stick_down)
var stick_drag: InputEventScreenDrag = InputEventScreenDrag.new()
stick_drag.index = 0
stick_drag.position = joy.global_position + joy.size / 2.0 + Vector2(joy.max_radius, 0.0)
controller._handle_drag(stick_drag)
if joy.direction.x < 0.9:
print("TEST_PIVOT_CONTROLS: FAIL (stick did not engage)")
quit(1)
return true
# Second finger lands on the clockwise pivot button.
var pivot_down: InputEventScreenTouch = InputEventScreenTouch.new()
pivot_down.index = 1
pivot_down.pressed = true
pivot_down.position = cw.global_position + cw.size / 2.0
controller._handle_touch(pivot_down)
var both_ok: bool = cw.pressed_down and joy.direction.x > 0.9
print("simultaneous: stick=%s pivot_held=%s" % [joy.direction, cw.pressed_down])
if not both_ok:
print("TEST_PIVOT_CONTROLS: FAIL (cannot pivot while steering)")
quit(1)
return true
angle_at_hold_start = _angle()
elapsed = 0.0
stage = 2
return false
2:
if elapsed < 0.4:
return false
# Both fingers still down: the formation must have kept turning
# while the stick kept re-issuing move orders every frame.
var turned_while_steering: float = absf(angle_difference(angle_at_hold_start, _angle()))
print("turned %.3f rad while steering" % turned_while_steering)
if turned_while_steering < 0.05:
print("TEST_PIVOT_CONTROLS: FAIL (no rotation while the stick was held)")
quit(1)
return true
# Steering is re-issued every frame; pivot in the middle of it.
fleet.move_in_direction(Vector2.RIGHT)
var before_moving_pivot: Vector2 = game_state.formation[1]
controller._pivot(PI / 2.0)
var after: Vector2 = game_state.formation[1]
fleet.move_in_direction(Vector2.RIGHT)
# The ship's target must reflect the NEW offset even though
# steering re-issued move orders straight afterwards.
var expected: Vector2 = fleet.fleet_target + after
var followed: bool = fleet.ships[1].move_target.distance_to(expected) < 0.01
var rotated: bool = after.distance_to(before_moving_pivot.rotated(PI / 2.0)) < 0.01
print("moving pivot: %s -> %s rotated=%s ship_target_ok=%s" % [
before_moving_pivot, after, rotated, followed
])
if rotated and followed:
print("TEST_PIVOT_CONTROLS: PASS")
quit(0)
else:
print("TEST_PIVOT_CONTROLS: FAIL (pivot lost while moving)")
quit(1)
return true
return false