extends SceneTree ## Headless smoke test for the player spawn + skin + animation pipeline. ## Boots the test level as a singleplayer match, spawns the local player, ## ticks frames, and verifies the visual model came up for both the ## procedural skin and a GLB skin. ## ## Run: godot --headless --path . -s res://debug/spawn_smoke_test.gd var _failures: Array = [] func _init() -> void: call_deferred("_run") func _run() -> void: await process_frame await process_frame var skin_mgr = root.get_node_or_null("/root/SkinManager") _check(skin_mgr != null, "SkinManager autoload exists") var audio_mgr = root.get_node_or_null("/root/AudioManager") _check(audio_mgr != null, "AudioManager autoload exists") if audio_mgr: _check(audio_mgr.has_sound("ak47_fire"), "AudioManager auto-registered ak47_fire") # Pick the GLB skin if its model file is present, else default. var glb_skin_id := "" if skin_mgr: for id in skin_mgr.get_skin_ids(): var s = skin_mgr.get_skin(id) if s.model_path != "" and ResourceLoader.exists(s.model_path): glb_skin_id = id break await _test_spawn_with_skin("default", false) if glb_skin_id != "": await _test_spawn_with_skin(glb_skin_id, true) else: print("NOTE: no GLB skin available, skipped skinned model test") print("\n=== SPAWN SMOKE SUMMARY ===") print("Failures: %d" % _failures.size()) for f in _failures: print("FAIL: ", f) quit(0 if _failures.is_empty() else 1) func _test_spawn_with_skin(skin_id: String, expect_skinned: bool) -> void: print("\n--- Spawn test with skin '%s' ---" % skin_id) var skin_mgr = root.get_node_or_null("/root/SkinManager") if skin_mgr: skin_mgr.set_active_skin(skin_id) var nm = root.get_node_or_null("/root/NetworkManager") if not _check(nm != null, "NetworkManager autoload exists"): return nm.start_singleplayer_match("Deathmatch") change_scene_to_file("res://scenes/maps/test_level/test_level.tscn") # Let the level build and the player spawn + settle. for i in 30: await process_frame var level = current_scene if not _check(level != null, "level scene loaded"): return var player = _find_player(level) if not _check(player != null, "player '1' spawned"): return _check(player.get_node_or_null("MovementStateMachine") != null, "state machine present") var visual = player.get_visual_model() if player.has_method("get_visual_model") else null _check(visual != null, "visual model present") if expect_skinned: var skinned = player.get_node_or_null("SkinnedModel") if _check(skinned != null, "SkinnedModel created for GLB skin"): _check(skinned.loaded, "GLB model loaded") _check(skinned.skeleton != null, "skeleton found in GLB") # The mesh must be bound to the skeleton, else it renders its bind # pose (T-pose) while the skeleton animates invisibly. var bound := false if skinned.skeleton: for mi in skinned.find_children("*", "MeshInstance3D", true, false): if mi.skin != null and mi.get_node_or_null(mi.skeleton) == skinned.skeleton: bound = true _check(bound, "skinned mesh is bound to the skeleton (won't T-pose)") if skinned.animation_player: # The AnimationTree drives playback now; the raw player is idle. _check(skinned._anim_tree != null and skinned._anim_tree.active, "animation tree active") print(" clips resolved: ", skinned._resolved_clips) # Drive some movement states through the same API the game uses. for state in ["ground", "air", "slide", "wall_run", "dash"]: skinned.update_state(state, 9.0, false) await process_frame _check(true, "state cycling did not crash") # Third-person toggle: camera swap + owner model visibility. if player.has_method("set_third_person"): var tp_cam = player.get_node_or_null( "HeadPivot/OrbitPivot/ShoulderOffset/ThirdPersonBoom/ThirdPersonCamera") _check(tp_cam != null, "third-person camera boom created") player.set_third_person(true) await process_frame _check(player.third_person, "toggled into third person") if tp_cam: _check(tp_cam.current, "third-person camera is current when toggled on") player.set_third_person(false) await process_frame _check(not player.third_person, "toggled back to first person") else: var humanoid = player.get_node_or_null("HumanoidModel") _check(humanoid != null and humanoid.visible, "procedural model visible for color skin") # Simulate a few physics frames of idle play. for i in 30: await physics_frame _check(is_instance_valid(player), "player survived 30 physics frames") func _find_player(node: Node) -> Node: if node is CharacterBody3D and node.name == "1" and node.has_method("get_visual_model"): return node for child in node.get_children(): var found := _find_player(child) if found: return found return null func _check(ok: bool, msg: String) -> bool: if ok: print(" OK: ", msg) else: print(" FAIL: ", msg) _failures.append(msg) return ok