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