Files
modbison/scripts/smoke_test_player.gd
daddy 87f78b0226
checkpoint / checkpoint (push) Successful in 1m34s
Third-person camera + fix the avatar customize preview
You couldn't see your own character because the camera was first-person
(inside the avatar's head), and the customize screen's 3D preview rendered
nothing.

- player.tscn: camera now hangs off a SpringArm3D on a CameraPivot, 4 m
  behind + above the player, pulled in on collision. player.gd: yaw still
  turns the body, pitch orbits the arm (starts tilted down to frame the
  avatar).
- avatar_customization_ui.gd: the preview SubViewport had own_world_3d=false,
  so it shared the Control-only main world -- no scenario, nothing rendered.
  Set own_world_3d=true, add a WorldEnvironment with ambient fill + a
  key/fill light pair, make the camera current, tweak framing.
- smoke_test_player: assert the third-person SpringArm rig (18/18).
- smoke_test_enter_world: wait longer for the scene change -- a real
  POST /join spawns a dedicated server, which is slow (9/9).

Client-side change -- rebuild the exe to get it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015jtxAiu5dAW4WxCNdGkrkJ
2026-09-05 23:13:07 +00:00

100 lines
4.1 KiB
GDScript

extends SceneTree
## Smoke test for the player character scene and its spawn behavior.
##
## Run with:
## godot --headless --path . --script res://scripts/smoke_test_player.gd
##
## 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
func _initialize() -> void:
print("SMOKE: starting player smoke test")
_run()
func _run() -> void:
await process_frame
print("SMOKE: loading game scene")
# Load scenes/game.tscn -- the real PC2 composition point (Level + Player
# as siblings, per docs/architecture/pc2_test_level.md §4), not the level
# scene directly. Use the same SceneTree API as the real game so
# `current_scene` is populated before the scene is added and its
# _ready() callbacks fire. That ordering is what the player's spawn
# lookup (get_tree().current_scene) depends on.
var packed: PackedScene = load("res://scenes/game.tscn")
var result := change_scene_to_packed(packed)
if result != OK:
printerr("SMOKE: failed to change scene, error ", result)
quit(1)
return
await scene_changed
var game: Node3D = current_scene as Node3D
print("SMOKE: game scene ready")
# 1. Verify the player node exists in the scene tree.
var player: Node = game.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: Node = player.get_node("CollisionShape3D")
_check("Player has CollisionShape3D", collision != null)
_check("CollisionShape3D has a shape",
collision is CollisionShape3D and (collision as CollisionShape3D).shape != null)
var mesh: Node = player.get_node("CapsuleMesh")
_check("Player has CapsuleMesh", 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 = game.get_node("Level/PlayerSpawn")
_check("PlayerSpawn marker exists", spawn_marker != null)
# The player's _ready() should have moved it to the spawn marker's
# global position. Compare global positions directly (not local) since
# Player and Level sit at different depths under Game.
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 and has the
# movement constants defined.
var script: GDScript = player.get_script() as GDScript
_check("Player has a script attached", script != null)
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"))
_check("JUMP_VELOCITY is positive (upward)", float(constants.get("JUMP_VELOCITY", -1.0)) > 0.0)
# 5. Verify the player has a Camera3D so the game is actually viewable
# from the player's perspective, not just a walking capsule with no view.
var camera: Node = player.find_child("Camera3D", true, false)
_check("Player has a Camera3D", camera is Camera3D)
_check("Camera3D is current", camera is Camera3D and (camera as Camera3D).current)
# Third-person rig: the camera hangs off a SpringArm3D (so it pulls in
# when a wall is behind the player) with real length.
var arm: Node = player.find_child("SpringArm3D", true, false)
_check("Camera is on a SpringArm3D (third-person)", arm is SpringArm3D)
_check("SpringArm has real length", arm is SpringArm3D and (arm as SpringArm3D).spring_length >= 2.0)
_check("Camera is a descendant of the SpringArm", camera != null and arm != null and arm.is_ancestor_of(camera))
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
else:
printerr(" FAIL: ", description)
_failed += 1