diff --git a/scripts/player.gd b/scripts/player.gd index 06a2f78..682e204 100644 --- a/scripts/player.gd +++ b/scripts/player.gd @@ -34,11 +34,19 @@ func _ready() -> void: # scene root. The lookup walks the full subtree (recursive=true) so # it finds the marker whether it is a direct child of the level or # nested deeper. - var spawn := get_tree().current_scene.find_child("PlayerSpawn", true, false) + # + # current_scene is null in headless --script mode and in any scene + # before the main scene is fully swapped in, so guard it rather than + # blindly dereferencing. + var current_scene: Node = get_tree().current_scene + var spawn: Node = null + if current_scene != null: + spawn = current_scene.find_child("PlayerSpawn", true, false) + if spawn is Node3D: - global_position = spawn.global_position + global_position = (spawn as Node3D).global_position # Align player forward with spawn yaw (optional polish). - var spawn_yaw := spawn.rotation.y + var spawn_yaw := (spawn as Node3D).rotation.y rotation.y = spawn_yaw else: push_warning("player.gd: no PlayerSpawn marker found; spawning at origin") diff --git a/scripts/smoke_test_player.gd b/scripts/smoke_test_player.gd index 20826d0..02e4c0a 100644 --- a/scripts/smoke_test_player.gd +++ b/scripts/smoke_test_player.gd @@ -7,8 +7,8 @@ extends SceneTree ## Loads the test level, verifies the player is present, spawned at the ## PlayerSpawn marker, and has the expected CharacterBody3D structure. -var passed := 0 -var failed := 0 +var _passed := 0 +var _failed := 0 func _initialize() -> void: @@ -20,60 +20,65 @@ func _run() -> void: await process_frame print("SMOKE: loading test level scene") - # Load the test level scene (which now includes the player). + # Load the test level scene (which includes the player). var packed: PackedScene = load("res://scenes/worlds/test_level.tscn") var level: Node3D = packed.instantiate() + + # Order matters: set_current_scene() requires the node's parent to already + # be root, so add_child() must come first. This mirrors what a real + # change_scene_to_packed() does at runtime. root.add_child(level) + current_scene = level + print("SMOKE: level instantiated, waiting for ready") await level.ready print("SMOKE: level ready") # 1. Verify the player node exists in the scene tree. - var player := level.get_node("Player") + var player: Node = level.get_node("Player") _check("Player node exists in test level", player != null) _check("Player is a CharacterBody3D", player is CharacterBody3D) # 2. Verify the player has the expected child structure. - var collision := player.get_node("CollisionShape3D") + var collision: Node = player.get_node("CollisionShape3D") _check("Player has CollisionShape3D", collision != null) - _check("CollisionShape3D has a shape", collision.shape != null) + _check("CollisionShape3D has a shape", + collision is CollisionShape3D and (collision as CollisionShape3D).shape != null) - var mesh := player.get_node("CapsuleMesh") + var mesh: Node = player.get_node("CapsuleMesh") _check("Player has CapsuleMesh", mesh != null) - _check("CapsuleMesh has a mesh", mesh.mesh != null) + _check("CapsuleMesh has a mesh", + mesh is MeshInstance3D and (mesh as MeshInstance3D).mesh != null) # 3. Verify the player spawned at the PlayerSpawn marker position. var spawn_marker: Marker3D = level.get_node("PlayerSpawn") _check("PlayerSpawn marker exists", spawn_marker != null) # The player's _ready() should have moved it to the spawn marker's - # global position. Since both are direct children of the root, their - # global positions equal their local positions. + # global position. Since both are direct children of the level root + # (and the level root is at the origin), their global positions equal + # their local positions. var expected_pos: Vector3 = spawn_marker.global_position _check("Player spawned at PlayerSpawn position", player.global_position == expected_pos) - # 4. Verify the player script class_name is correct. - var script := player.get_script() + # 4. Verify the player script class_name is correct and has the + # movement constants defined. + var script: GDScript = player.get_script() as GDScript _check("Player has a script attached", script != null) - _check("Player script is Player class", script is GDScript) + var constants: Dictionary = script.get_script_constant_map() + _check("Player has WALK_SPEED constant", constants.has("WALK_SPEED")) + _check("Player has GRAVITY constant", constants.has("GRAVITY")) + _check("Player has JUMP_VELOCITY constant", constants.has("JUMP_VELOCITY")) - # 5. Verify the player has movement constants defined. - _check("Player has WALK_SPEED constant", - script.get_constant("WALK_SPEED") != null) - _check("Player has GRAVITY constant", - script.get_constant("GRAVITY") != null) - _check("Player has JUMP_VELOCITY constant", - script.get_constant("JUMP_VELOCITY") != null) - - print("\n=== SMOKE RESULT: %d/%d passed ===" % [passed, passed + failed]) - quit(0 if failed == 0 else 1) + print("\n=== SMOKE RESULT: %d/%d passed ===" % [_passed, _passed + _failed]) + quit(0 if _failed == 0 else 1) func _check(description: String, condition: bool) -> void: if condition: print(" PASS: ", description) - passed += 1 + _passed += 1 else: printerr(" FAIL: ", description) - failed += 1 + _failed += 1