extends SceneTree const PlayerView = preload("res://ui/player_view.gd") const EquipModel = preload("res://ui/equip_model.gd") var failures: Array[String] = [] func _initialize() -> void: call_deferred("run") func check(ok: bool, message: String) -> void: if not ok: failures.append(message) printerr(message) func run() -> void: for race in 8: var pv := PlayerView.new() root.add_child(pv) if not pv.build(AssetRoot.path(), race): check(false, "race %d failed to build" % race) pv.queue_free() continue pv.anim.set_process(false) pv.anim.set("blend_time", 0.0) var equip := EquipModel.new() pv.add_child(equip) equip.setup_remote(null, func() -> Node: return pv, AssetRoot.path(), race, [0, 0, 0, 0]) # MSM is an independent asset authority for the equipped body's gender. var body_dir := String(pv.model.get("gr2_path")).get_base_dir().to_lower() check(body_dir == pv.motion_dir.get_base_dir().to_lower(), "race %d: MSM body and animation directories differ" % race) check(String(pv.model.get("hair_gr2")).to_lower().begins_with(body_dir + "/"), "race %d: hair is from a different resource family" % race) for motion in ["wait", "walk", "run", "attack", "damage", "dead"]: pv.set_anim_state(motion) # damage is registered as two equal-weight variants (damage / damage_1). var variants: Array = PlayerView.REACTION_FILES.get(motion, [motion]) check(String(pv.anim.get("anim_path")).get_file().get_basename() in variants, "race %d: missing %s motion" % [race, motion]) for fraction in [0.0, 0.5, 1.0]: pv.anim.set("time", float(pv.anim.call("get_duration")) * fraction) for mi in pv.model.find_children("*", "MeshInstance3D", true, false): var bounds: AABB = mi.get_aabb() check(bounds.position.is_finite() and bounds.size.is_finite() and bounds.size.length() > 0, "race %d: invalid bounds in %s" % [race, motion]) pv.queue_free() await process_frame print("race_motion_assembly_test: failures=", failures.size(), " (8 races, 6 motions, 3 samples)") quit(0 if failures.is_empty() else 1)