981ebf1910
Remove tools/fstest.html scratch page used to probe browser fullscreen/orientation APIs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EznnY8rH2dXhtono1kwsXg
124 lines
4.5 KiB
GDScript
124 lines
4.5 KiB
GDScript
extends SceneTree
|
|
## Guardrail for the on-screen joystick's self-healing. The reported bug is a stick that
|
|
## "disappears / goes non-responsive": a touchend dropped during a resize/orientation flip (or
|
|
## an app backgrounding) leaves _stick_active stuck true, so the ghost hint hides and no new
|
|
## stick can start — and the bull keeps coasting on the held move actions. touch_controls.gd
|
|
## now resets on relayout + focus-out, prunes orphaned touches, and lets the latest left-zone
|
|
## touch re-acquire the stick. This drives synthetic touch events and asserts the stick never
|
|
## stays stranded and never leaves movement held.
|
|
## Run: godot --headless --script res://tests/touch_controls_test.gd
|
|
|
|
const _MOVE := [&"move_forward", &"move_back", &"move_left", &"move_right"]
|
|
|
|
var _pass := 0
|
|
var _fail := 0
|
|
var _tc: CanvasLayer
|
|
|
|
|
|
func _init() -> void:
|
|
_run.call_deferred()
|
|
|
|
|
|
func _ok(c: bool, m: String) -> void:
|
|
if c:
|
|
_pass += 1
|
|
print(" PASS: ", m)
|
|
else:
|
|
_fail += 1
|
|
print(" FAIL: ", m)
|
|
|
|
|
|
func _press(index: int, pos: Vector2) -> void:
|
|
var e := InputEventScreenTouch.new()
|
|
e.index = index
|
|
e.position = pos
|
|
e.pressed = true
|
|
_tc._input(e)
|
|
|
|
|
|
func _release(index: int, pos: Vector2) -> void:
|
|
var e := InputEventScreenTouch.new()
|
|
e.index = index
|
|
e.position = pos
|
|
e.pressed = false
|
|
_tc._input(e)
|
|
|
|
|
|
func _drag(index: int, pos: Vector2) -> void:
|
|
var e := InputEventScreenDrag.new()
|
|
e.index = index
|
|
e.position = pos
|
|
_tc._input(e)
|
|
|
|
|
|
func _any_move_held() -> bool:
|
|
for a: StringName in _MOVE:
|
|
if Input.is_action_pressed(a):
|
|
return true
|
|
return false
|
|
|
|
|
|
# A point safely inside the left-hand joystick zone (independent of viewport size), plus the
|
|
# same point pushed a full radius "up" so the stick engages move_forward.
|
|
func _stick_origin() -> Vector2:
|
|
return _tc._stick_zone.position + _tc._stick_zone.size * Vector2(0.25, 0.5)
|
|
|
|
|
|
func _run() -> void:
|
|
var dp: Node = root.get_node("/root/DP")
|
|
var controls: Node = root.get_node("/root/Controls")
|
|
dp.call("set_value", "force_touch_controls", true)
|
|
_ok(controls.call("use_touch_ui"), "force flag makes use_touch_ui() true (touch UI live)")
|
|
|
|
# Headless boots a 64x64 root viewport, which collapses every UI rect on top of each other.
|
|
# Give it a real phone-ish landscape size so the joystick zone / buttons lay out sanely.
|
|
root.size = Vector2i(1152, 648)
|
|
_tc = (load("res://touch_controls.gd") as Script).new()
|
|
root.add_child(_tc)
|
|
await process_frame # _ready builds _surface and runs the first _relayout
|
|
_tc._relayout() # ensure the layout reflects the size set above
|
|
|
|
var o := _stick_origin()
|
|
var up := o + Vector2(0.0, -_tc._stick_radius)
|
|
|
|
# (a) A resize mid-drag (dropped touchend) must not strand the stick or keep the bull moving.
|
|
_tc._reset_touch_state()
|
|
_press(0, o)
|
|
_drag(0, up)
|
|
_ok(_tc._stick_active and _any_move_held(), "drag engages the stick and holds movement")
|
|
_tc._relayout() # the resize/orientation flip that used to swallow the release
|
|
_ok(not _tc._stick_active, "a relayout mid-drag stands the stick down (not stranded)")
|
|
_ok(not _any_move_held(), "movement is released on relayout (bull doesn't coast)")
|
|
|
|
# (b) App backgrounded / focus lost — same self-heal via the notification hook.
|
|
_tc._reset_touch_state()
|
|
_press(0, o)
|
|
_drag(0, up)
|
|
_tc._notification(Node.NOTIFICATION_APPLICATION_FOCUS_OUT)
|
|
_ok(not _tc._stick_active and not _any_move_held(), "focus-out clears the stick and movement")
|
|
|
|
# (c) A stranded stick self-heals: with finger 0's release lost, a fresh finger 1 in the zone
|
|
# re-acquires the stick; finger 0's late release must NOT kill finger 1's stick.
|
|
_tc._reset_touch_state()
|
|
_press(0, o) # finger 0 owns the stick
|
|
_press(1, o + Vector2(20, 10)) # finger 1 lands while 0 is still (wrongly) held → takes over
|
|
_ok(_tc._stick_active and _tc._stick_index == 1, "latest left-zone touch re-acquires the stick")
|
|
_release(0, o) # the stranded finger finally reports up
|
|
_ok(_tc._stick_active and _tc._stick_index == 1, "a superseded finger's release doesn't end the stick")
|
|
_release(1, o)
|
|
_ok(not _tc._stick_active, "releasing the owning finger ends the stick")
|
|
|
|
# (d) Watchdog: a touch that vanishes from tracking (no release event at all) is pruned.
|
|
_tc._reset_touch_state()
|
|
_press(0, o)
|
|
_tc._touches.erase(0) # simulate the finger silently disappearing
|
|
_tc._process(0.016) # prune runs each frame
|
|
_ok(not _tc._stick_active and not _any_move_held(), "watchdog prunes a vanished touch and frees the stick")
|
|
|
|
_finish()
|
|
|
|
|
|
func _finish() -> void:
|
|
print("Results: %d passed, %d failed" % [_pass, _fail])
|
|
quit(1 if _fail > 0 else 0)
|