extends SceneTree ## Headless M02 profile/capability gate regression for debug player movement. const MOVEMENT_CAPABILITIES_PATH := "res://src/gameplay/movement/player_movement_capabilities.gd" const MOVEMENT_CONTROLLER_PATH := "res://src/gameplay/movement/local_player_movement_controller.gd" const PLAYER_CONTROLLER_PATH := "res://src/scenes/player/third_person_wow_controller.gd" const REGRESSION_SCENE_PATH := "res://src/tests/scenes/player_input_regression.tscn" func _initialize() -> void: _run_verification.call_deferred() func _run_verification() -> void: var failures: Array[String] = [] _verify_capability_profiles(failures) _verify_movement_gate(failures) _verify_real_scene_profiles(failures) _verify_source_boundary(failures) if not failures.is_empty(): for failure in failures: push_error("PLAYER_MOVEMENT_CAPABILITIES: %s" % failure) quit(1) return print("PLAYER_MOVEMENT_CAPABILITIES PASS profiles=3 sprint=2 flight=2 scenes=2 boundary=1") quit(0) func _verify_capability_profiles(failures: Array[String]) -> void: var render_sandbox := PlayerMovementCapabilities.render_sandbox() _expect_true(render_sandbox.profile_id == PlayerMovementCapabilities.RENDER_SANDBOX_PROFILE_ID, "sandbox profile identity", failures) _expect_true(render_sandbox.allows_debug_sprint, "sandbox sprint capability", failures) _expect_true(render_sandbox.allows_debug_free_flight, "sandbox flight capability", failures) var blizzlike := PlayerMovementCapabilities.blizzlike_335() _expect_true(blizzlike.profile_id == PlayerMovementCapabilities.BLIZZLIKE_335_PROFILE_ID, "blizzlike profile identity", failures) _expect_true(not blizzlike.allows_debug_sprint, "blizzlike rejects sprint", failures) _expect_true(not blizzlike.allows_debug_free_flight, "blizzlike rejects flight", failures) var unknown_profile := PlayerMovementCapabilities.for_profile_id(&"UnknownProfile") _expect_true(unknown_profile.profile_id == PlayerMovementCapabilities.BLIZZLIKE_335_PROFILE_ID, "unknown profile fails closed", failures) _expect_true(not unknown_profile.allows_debug_sprint, "unknown profile rejects sprint", failures) _expect_true(not unknown_profile.allows_debug_free_flight, "unknown profile rejects flight", failures) func _verify_movement_gate(failures: Array[String]) -> void: var sprint_intent := MoveIntent.new(1.0, 0.0, 0.0, true) var sandbox_controller := _new_controller(PlayerMovementCapabilities.render_sandbox()) sandbox_controller.advance(sprint_intent, Basis.IDENTITY, 2.0) _expect_vector_near(sandbox_controller.godot_world_velocity_units_per_second, Vector3(0.0, 0.0, -42.0), "sandbox sprint applied", failures) _expect_true(sandbox_controller.toggle_sandbox_flight(), "sandbox flight enabled", failures) var blizzlike_controller := _new_controller(PlayerMovementCapabilities.blizzlike_335()) blizzlike_controller.advance(sprint_intent, Basis.IDENTITY, 2.0) _expect_vector_near(blizzlike_controller.godot_world_velocity_units_per_second, Vector3(0.0, 0.0, -7.0), "blizzlike sprint ignored", failures) _expect_true(not blizzlike_controller.toggle_sandbox_flight(), "blizzlike flight rejected", failures) _expect_true(not blizzlike_controller.is_flight_enabled, "blizzlike remains grounded", failures) var safe_default_controller := LocalPlayerMovementController.new(7.0, 4.5, 4.5, 7.0, 28.0, 6.0) _expect_true(safe_default_controller.movement_profile_id == PlayerMovementCapabilities.BLIZZLIKE_335_PROFILE_ID, "missing capability defaults safe", failures) func _verify_real_scene_profiles(failures: Array[String]) -> void: var sandbox_player := _instantiate_player("RenderSandbox", failures) if sandbox_player != null: _press_sprint_forward_for_two_seconds(sandbox_player) var sandbox_movement: LocalPlayerMovementController = sandbox_player.get("_local_movement_controller") _expect_vector_near(sandbox_movement.godot_world_velocity_units_per_second, Vector3(0.0, 0.0, -42.0), "sandbox scene sprint", failures) sandbox_player.call("_unhandled_input", _action_event(PlayerInputActions.DEBUG_TOGGLE_FLIGHT)) _expect_true(sandbox_movement.is_flight_enabled, "sandbox scene flight", failures) sandbox_player.free() var blizzlike_player := _instantiate_player("Blizzlike335", failures) if blizzlike_player != null: _press_sprint_forward_for_two_seconds(blizzlike_player) var blizzlike_movement: LocalPlayerMovementController = blizzlike_player.get("_local_movement_controller") _expect_vector_near(blizzlike_movement.godot_world_velocity_units_per_second, Vector3(0.0, 0.0, -7.0), "blizzlike scene sprint rejected", failures) blizzlike_player.call("_unhandled_input", _action_event(PlayerInputActions.DEBUG_TOGGLE_FLIGHT)) _expect_true(not blizzlike_movement.is_flight_enabled, "blizzlike scene flight rejected", failures) blizzlike_player.free() func _instantiate_player(profile_id: String, failures: Array[String]) -> CharacterBody3D: var packed_scene := load(REGRESSION_SCENE_PATH) as PackedScene if packed_scene == null: failures.append("regression scene loads") return null var player := packed_scene.instantiate() as CharacterBody3D if player == null: failures.append("regression player instantiates") return null player.set("movement_profile_id", profile_id) root.add_child(player) player.set_physics_process(false) return player func _press_sprint_forward_for_two_seconds(player: CharacterBody3D) -> void: Input.action_press(PlayerInputActions.MOVE_FORWARD) Input.action_press(PlayerInputActions.DEBUG_SPRINT) player.call("_physics_process", 2.0) Input.action_release(PlayerInputActions.DEBUG_SPRINT) Input.action_release(PlayerInputActions.MOVE_FORWARD) func _action_event(action_name: StringName) -> InputEventAction: var input_event := InputEventAction.new() input_event.action = action_name input_event.pressed = true return input_event func _verify_source_boundary(failures: Array[String]) -> void: var capability_source := _read_text(MOVEMENT_CAPABILITIES_PATH, failures) for forbidden_text in ["extends Node", "extends Resource", "Input.", "CharacterBody3D"]: _expect_true(not capability_source.contains(forbidden_text), "capability omits %s" % forbidden_text, failures) _expect_true(not capability_source.contains("\n\tset:"), "capability exposes no property setters", failures) var movement_source := _read_text(MOVEMENT_CONTROLLER_PATH, failures) _expect_true(movement_source.contains("allows_debug_sprint"), "movement gates sprint capability", failures) _expect_true(movement_source.contains("allows_debug_free_flight"), "movement gates flight capability", failures) var player_source := _read_text(PLAYER_CONTROLLER_PATH, failures) _expect_true(player_source.contains("for_profile_id(StringName(movement_profile_id))"), "scene maps profile once", failures) _expect_true(not player_source.contains('movement_profile_id == "'), "scene omits profile branches", failures) func _new_controller(capabilities: PlayerMovementCapabilities) -> LocalPlayerMovementController: return LocalPlayerMovementController.new(7.0, 4.5, 4.5, 7.0, 28.0, 6.0, capabilities) func _read_text(path: String, failures: Array[String]) -> String: var file := FileAccess.open(path, FileAccess.READ) if file == null: failures.append("cannot open %s" % path) return "" return file.get_as_text() func _expect_vector_near(actual_value: Vector3, expected_value: Vector3, label: String, failures: Array[String]) -> void: if not actual_value.is_equal_approx(expected_value): failures.append("%s expected %s, got %s" % [label, expected_value, actual_value]) func _expect_true(actual_value: bool, label: String, failures: Array[String]) -> void: if not actual_value: failures.append("%s expected true" % label)