742c415885
Work-Package: M02-GMP-SANDBOX-CAPABILITIES-001 Agent: sindo-main-codex Tests: capability/input/movement/terrain/camera/presentation; coordinate/streaming/docs/coordination; renderer dry-run 7/7 Fidelity: proves debug sprint/free-flight exclusion in Blizzlike335; no positive movement parity claim
149 lines
7.6 KiB
GDScript
149 lines
7.6 KiB
GDScript
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)
|