extends SceneTree ## Smoke test for the avatar catalog/slot/loadout/system layers AND the full ## assembly path against the placeholder GLB asset pack ## (assets/avatars/*.glb, generated by scripts/tools/gen_placeholder_avatars.gd). ## ## Covers: catalog parsing, slot registry, loadout equip/validate/round-trip, ## the AvatarSystem autoload wiring, and load_avatar() actually producing a ## Skeleton3D with the canonical rig, a visible body mesh, and equipped ## attachments mounted to the right bones. ## ## Run with: ## godot --headless --path . --script res://scripts/smoke_test_avatar_system.gd # Preloaded rather than referenced by global class_name so this test also # runs from a cold cache (a bare `--script` run with no prior --import). const AvatarLoadout := preload("res://scripts/avatar/avatar_loadout.gd") var passed := 0 var failed := 0 func _initialize() -> void: _run() func _run() -> void: await process_frame # 1. AvatarSystem is a real autoload, not just a loose script. var system := root.get_node_or_null("AvatarSystem") if not _check("AvatarSystem autoload exists", system != null): _finish() return # 2. The real catalog.json (committed to assets/avatars/) actually parses. var err: Error = system.load_catalog() _check("catalog loads without error", err == OK) _check("AvatarSystem reports catalog loaded", system.is_catalog_loaded) var base_bodies: Array[String] = system.get_assets_for_slot("base_body") _check("catalog has at least one base_body entry", base_bodies.size() > 0) # 3. Loadout: create, equip, validate against the real catalog. # base_body is its own dedicated field on AvatarLoadout, not something # set via the generic per-slot set_equipment() -- AvatarSlotRegistry's # SLOT_IDS deliberately excludes "base_body" (it's the foundational # model you start from, not a layered attachment slot). var loadout := AvatarLoadout.new() if base_bodies.size() > 0: loadout.base_body = base_bodies[0] _check("loadout has base_body set", base_bodies.size() == 0 or not loadout.base_body.is_empty()) var hair_options: Array[String] = system.get_assets_for_slot("hair") _check("hair options include hair_waterbuffalo_01", hair_options.has("hair_waterbuffalo_01")) if hair_options.size() > 0: loadout.set_equipment("hair", "hair_waterbuffalo_01" if hair_options.has("hair_waterbuffalo_01") else hair_options[0]) _check("equipping a real catalog hair item succeeds", loadout.slots.get("hair") == ("hair_waterbuffalo_01" if hair_options.has("hair_waterbuffalo_01") else hair_options[0])) # 4. Loadout serializes and survives a round trip. var json_str: String = loadout.to_json() _check("loadout serializes to non-empty JSON", json_str.length() > 0) var restored: AvatarLoadout = system.load_loadout_from_json(json_str) _check("loadout round-trips through JSON", restored != null and restored.is_valid()) # 5. Full assembly against the placeholder GLB pack. if base_bodies.size() > 0: var errors := [] as Array[String] var error_handler := func(msg): errors.append(msg) system.loader.load_error.connect(error_handler) var chest_options: Array[String] = system.get_assets_for_slot("chest") if chest_options.size() > 0: loadout.set_equipment("chest", chest_options[0]) var acc_options: Array[String] = system.get_assets_for_slot("accessory") if acc_options.size() > 0: loadout.add_accessory(acc_options[0]) var avatar: Node3D = system.load_avatar(loadout) system.loader.load_error.disconnect(error_handler) _check("load_avatar returns a node (no load_error: %s)" % str(errors), avatar != null and errors.is_empty()) if avatar: var skel := avatar.get_node_or_null("Skeleton3D") as Skeleton3D _check("assembled avatar has a Skeleton3D", skel != null) if skel: _check("skeleton carries the full canonical rig (24 bones)", skel.get_bone_count() == 24) _check("canonical bones resolve by name (Head, LeftHand, RightFoot)", skel.find_bone("Head") != -1 and skel.find_bone("LeftHand") != -1 and skel.find_bone("RightFoot") != -1) var body_meshes := 0 var bone_mounts := 0 for c in skel.get_children(): if c is MeshInstance3D and c.visible: body_meshes += 1 if c is BoneAttachment3D: bone_mounts += 1 _check("a visible body mesh is present", body_meshes >= 1) _check("equipped hair + chest are mounted to bones", bone_mounts >= 2) else: print(" SKIP: no base_body entries in catalog") _finish() func _check(description: String, condition: bool) -> bool: if condition: print(" PASS: ", description) passed += 1 else: printerr(" FAIL: ", description) failed += 1 return condition func _finish() -> void: print("\n=== SMOKE RESULT: %d/%d passed ===" % [passed, passed + failed]) quit(0 if failed == 0 else 1)