Files
modbison/scripts/smoke_test_avatar_system.gd

123 lines
4.7 KiB
GDScript

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)