extends SceneTree const PlayerView = preload("res://ui/player_view.gd") var failures := 0 func check(ok: bool, message: String) -> void: if not ok: failures += 1 printerr("FAIL: " + message) func _init() -> void: call_deferred("run") func run() -> void: Metin2AnimPlayer.clear_clip_cache() var a := PlayerView.new() var b := PlayerView.new() for view in [a, b]: check(view.build(AssetRoot.path(), 0), "warrior builds") root.add_child(view) view.anim.set_process(false) check(Metin2AnimPlayer.get_clip_cache_stats().misses == 1, "two actors decode idle only once") a.anim.set_time(0.3) b.anim.set_time(0.9) check(not is_equal_approx(a.anim.get_time(), b.anim.get_time()), "playback clocks remain independent") for i in 3: for state in ["run", "attack", "wait"]: for view in [a, b]: view.set_anim_state(state) view.anim.set_time(0.2) var stats: Dictionary = Metin2AnimPlayer.get_clip_cache_stats() check(stats.misses == 3 and stats.hits >= 17, "hot motion switches reuse decoded clips") check(stats.entries <= 32 and stats.section_bytes <= 64 * 1024 * 1024, "cache has entry and decoded-section budget") check(stats.msa_misses == 3 and stats.msa_hits >= 17, "hot switches reuse parsed MSA metadata") check(stats.msa_entries <= 128, "MSA cache has an entry limit") Metin2AnimPlayer.clear_clip_cache() check(Metin2AnimPlayer.get_clip_cache_stats().msa_entries == 0, "MSA cache clears with clips") a.anim.set_time(0.5) check(a.model.get_visual_aabb().size.length() > 0, "clearing cache does not invalidate active clip") a.free() b.free() await process_frame print("animation_cache_test: failures=%d stats=%s" % [failures, stats]) quit(1 if failures else 0)