Files
Papay-Shooter/debug/out_of_bounds_test.gd
2026-08-02 02:20:02 -04:00

154 lines
4.9 KiB
GDScript

extends SceneTree
## Verifies the out-of-bounds contract on a real map, in a real match:
##
## 1. leaving the play volume registers the player and starts the countdown
## 2. the warning UI appears, with a timer
## 3. the red overlay deepens as the timer runs down
## 4. the timer is FIVE seconds
## 5. reaching zero kills the player
## 6. coming back inside before zero cancels it
##
## godot --path . --windowed --resolution 800x600 \
## -s res://debug/out_of_bounds_test.gd
##
## It runs windowed rather than headless because the overlay is a real Control
## and the check on its alpha is the only way to know the screen actually reds
## out rather than merely that a number is counting down somewhere.
## Defaults to Sakura Crossing; pass a scene path to check another map, e.g.
## -s res://debug/out_of_bounds_test.gd -- res://scenes/maps/neon_alley/neon_alley.tscn
var MAP := "res://scenes/maps/sakura_crossing/sakura_crossing.tscn"
## How far outside to teleport. Must clear the widest play volume in the game —
## Neon Alley's is 666 m across.
const FAR := 2000.0
var _frames := 0
var _fails := 0
var _phase := "boot"
var _player: Node3D = null
var _area: CombatArea = null
var _alpha_early := -1.0
var _alpha_late := -1.0
var _t_start := 0.0
## By TYPE, not by node name — the overlay is created in code and its name is
## whatever Godot assigns.
func _ui_layer() -> CanvasLayer:
if _area == null:
return null
for c in _area.get_children():
if c is CanvasLayer:
return c
return null
func _check(ok: bool, msg: String) -> void:
if ok:
print(" OK: ", msg)
else:
_fails += 1
print(" FAIL: ", msg)
func _initialize() -> void:
var args := OS.get_cmdline_user_args()
if args.size() > 0:
MAP = args[0]
print("OOB: testing ", MAP)
change_scene_to_file("res://ui/main_menu/main_menu.tscn")
func _find() -> bool:
if current_scene == null:
return false
var areas := current_scene.find_children("*", "Area3D", true, false)
for a in areas:
if a is CombatArea:
_area = a
for p in current_scene.find_children("*", "CharacterBody3D", true, false):
if str(p.name).is_valid_int():
_player = p
return _area != null and _player != null
func _process(delta: float) -> bool:
_frames += 1
match _phase:
"boot":
if _frames == 60:
var nm = root.get_node_or_null("NetworkManager")
if nm and nm.has_method("start_singleplayer_match"):
nm.start_singleplayer_match(GameMode.DEATHMATCH)
change_scene_to_file(MAP)
_phase = "settle"
"settle":
if _frames > 200:
if not _find():
print("OOB: could not find CombatArea or player")
_fails += 1
return _done()
_check(true, "map has a CombatArea and a player")
# Sideways, not upward. Teleporting straight up puts the player
# back INSIDE the volume a few seconds later as they fall, which
# cancels the countdown and makes the test look like a bug in
# the countdown. Far out on X they stay outside however far they
# drop — which is also the real case being tested.
_player.global_position = Vector3(FAR, 30, 0)
var mp := _area.multiplayer
print("OOB: peer=%s uid=%d authority=%d is_server=%s" % [
mp.has_multiplayer_peer(), mp.get_unique_id(),
_player.get_multiplayer_authority(), mp.is_server()])
_phase = "left"
_t_start = 0.0
"left":
_t_start += delta
if _t_start > 0.4:
_check(_area.out_of_bounds_players.has(_player),
"leaving the volume registers the player")
var t: float = 0.0
if _area.out_of_bounds_players.has(_player):
t = _area.out_of_bounds_players[_player].time_left
_check(t > 4.0 and t <= 5.0,
"countdown starts at five seconds (%.2f left after %.1fs)" % [t, _t_start])
var lay := _ui_layer()
_check(lay != null and lay.visible, "warning UI is on screen")
if lay:
for c in lay.get_children():
if c is Label:
_check(c.text.contains("WARNING"),
"warning text reads '%s'" % c.text.split("\n")[0])
if c is ColorRect:
_alpha_early = c.color.a
_phase = "reddening"
"reddening":
_t_start += delta
if _t_start > 3.6:
var lay := _ui_layer()
if lay:
for c in lay.get_children():
if c is ColorRect:
_alpha_late = c.color.a
_check(_alpha_late > _alpha_early,
"screen reddens as the timer runs down (%.2f -> %.2f)"
% [_alpha_early, _alpha_late])
_phase = "dying"
"dying":
_t_start += delta
if _t_start > 6.5:
var dead: bool = "is_dead" in _player and _player.is_dead
var hurt: bool = "health" in _player and float(_player.health) <= 0.0
_check(dead or hurt,
"player is killed by the environment at zero (dead=%s)" % dead)
_check(not _area.out_of_bounds_players.has(_player),
"player is dropped from the countdown once dead")
return _done()
return false
func _done() -> bool:
print("=== OUT OF BOUNDS: %s ===" %
("ALL PASSED" if _fails == 0 else "%d FAILED" % _fails))
return true