extends SceneTree ## Headless M02 unit and boundary regression for local player movement state. 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" func _initialize() -> void: var failures: Array[String] = [] _verify_directional_speeds(failures) _verify_acceleration_and_deceleration(failures) _verify_sandbox_sprint(failures) _verify_flight_state_and_basis(failures) _verify_keyboard_turn(failures) _verify_invalid_delta(failures) _verify_scene_boundary(failures) if not failures.is_empty(): for failure in failures: push_error("LOCAL_PLAYER_MOVEMENT: %s" % failure) quit(1) return print("LOCAL_PLAYER_MOVEMENT PASS cases=15 state_transitions=2 scene_boundary=1") quit(0) func _verify_directional_speeds(failures: Array[String]) -> void: var forward_controller := _new_controller() var forward_displacement := forward_controller.advance(_intent(1.0, 0.0), Basis.IDENTITY, 1.0) _expect_vector_near(forward_controller.godot_world_velocity_units_per_second, Vector3(0.0, 0.0, -7.0), "forward velocity", failures) _expect_vector_near(forward_displacement, Vector3(0.0, 0.0, -7.0), "forward displacement", failures) var backward_controller := _new_controller() backward_controller.advance(_intent(-1.0, 0.0), Basis.IDENTITY, 1.0) _expect_vector_near(backward_controller.godot_world_velocity_units_per_second, Vector3(0.0, 0.0, 4.5), "backward velocity", failures) var strafe_controller := _new_controller() strafe_controller.advance(_intent(0.0, 1.0), Basis.IDENTITY, 1.0) _expect_vector_near(strafe_controller.godot_world_velocity_units_per_second, Vector3(4.5, 0.0, 0.0), "strafe velocity", failures) var rotated_controller := _new_controller() var rotated_basis := Basis(Vector3.UP, deg_to_rad(90.0)) rotated_controller.advance(_intent(1.0, 0.0), rotated_basis, 1.0) _expect_vector_near( rotated_controller.godot_world_velocity_units_per_second, (-rotated_basis.z).normalized() * 7.0, "rotated character basis", failures ) func _verify_acceleration_and_deceleration(failures: Array[String]) -> void: var controller := _new_controller() var forward_intent := _intent(1.0, 0.0) _expect_vector_near(controller.advance(forward_intent, Basis.IDENTITY, 0.1), Vector3(0.0, 0.0, -0.28), "first acceleration displacement", failures) _expect_vector_near(controller.godot_world_velocity_units_per_second, Vector3(0.0, 0.0, -2.8), "first acceleration velocity", failures) controller.advance(forward_intent, Basis.IDENTITY, 0.1) _expect_vector_near(controller.godot_world_velocity_units_per_second, Vector3(0.0, 0.0, -5.6), "second acceleration velocity", failures) controller.advance(_intent(0.0, 0.0), Basis.IDENTITY, 0.1) _expect_vector_near(controller.godot_world_velocity_units_per_second, Vector3(0.0, 0.0, -2.8), "deceleration velocity", failures) func _verify_sandbox_sprint(failures: Array[String]) -> void: var controller := _new_controller() controller.advance(_intent(1.0, 0.0, 0.0, true), Basis.IDENTITY, 2.0) _expect_vector_near(controller.godot_world_velocity_units_per_second, Vector3(0.0, 0.0, -42.0), "sandbox sprint velocity", failures) func _verify_flight_state_and_basis(failures: Array[String]) -> void: var controller := _new_controller() controller.advance(_intent(1.0, 0.0), Basis.IDENTITY, 1.0) _expect_true(controller.toggle_sandbox_flight(), "flight toggles on", failures) _expect_vector_near(controller.godot_world_velocity_units_per_second, Vector3.ZERO, "flight toggle clears velocity", failures) var pitched_basis := Basis(Vector3.RIGHT, deg_to_rad(30.0)) controller.advance(_intent(1.0, 0.0, 1.0), pitched_basis, 1.0) var expected_flight_velocity := (-pitched_basis.z).normalized() * 7.0 + Vector3.UP * 7.0 _expect_vector_near(controller.godot_world_velocity_units_per_second, expected_flight_velocity, "camera-relative flight velocity", failures) _expect_true(not controller.toggle_sandbox_flight(), "flight toggles off", failures) _expect_vector_near(controller.godot_world_velocity_units_per_second, Vector3.ZERO, "landing toggle clears velocity", failures) func _verify_invalid_delta(failures: Array[String]) -> void: var controller := _new_controller() var displacement := controller.advance(_intent(1.0, 0.0), Basis.IDENTITY, -1.0) _expect_vector_near(displacement, Vector3.ZERO, "negative delta displacement", failures) _expect_vector_near(controller.godot_world_velocity_units_per_second, Vector3.ZERO, "negative delta state", failures) func _verify_keyboard_turn(failures: Array[String]) -> void: var controller := _new_controller() _expect_near(controller.calculate_yaw_delta_radians(_intent(0.0, 0.0, 0.0, false, 1.0), 0.5), -PI * 0.5, "right turn yaw", failures) _expect_near(controller.calculate_yaw_delta_radians(_intent(0.0, 0.0, 0.0, false, -1.0), 0.5), PI * 0.5, "left turn yaw", failures) _expect_near(controller.calculate_yaw_delta_radians(_intent(0.0, 0.0, 0.0, false, 1.0), -1.0), 0.0, "negative turn delta", failures) func _verify_scene_boundary(failures: Array[String]) -> void: var movement_source := _read_text(MOVEMENT_CONTROLLER_PATH, failures) for forbidden_text in ["extends Node", "extends Resource", "Input.", "Camera3D", "ADTLoader", "global_position"]: _expect_true(not movement_source.contains(forbidden_text), "movement controller omits %s" % forbidden_text, failures) var player_source := _read_text(PLAYER_CONTROLLER_PATH, failures) _expect_true(player_source.contains("_local_movement_controller.advance"), "scene delegates movement update", failures) _expect_true(not player_source.contains("move_toward(target_velocity"), "scene omits velocity integration", failures) _expect_true(not player_source.contains("func _movement_vector"), "scene omits movement-vector calculation", failures) _expect_true(not player_source.contains("_horizontal_velocity"), "scene omits movement velocity state", failures) _expect_true(not player_source.contains("var _flight_enabled"), "scene omits flight state", failures) func _new_controller() -> LocalPlayerMovementController: return LocalPlayerMovementController.new( 7.0, 4.5, 4.5, 7.0, 28.0, 6.0, PlayerMovementCapabilities.render_sandbox() ) func _intent( forward_axis: float, strafe_axis: float, vertical_axis: float = 0.0, sprint_requested: bool = false, turn_axis: float = 0.0 ) -> MoveIntent: return MoveIntent.new(forward_axis, strafe_axis, vertical_axis, sprint_requested, turn_axis) 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_near(actual_value: float, expected_value: float, label: String, failures: Array[String]) -> void: if absf(actual_value - expected_value) > 0.000001: failures.append("%s expected %.6f, got %.6f" % [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)