Optimization: 2025 Edition

Introduction

In September of last year, I created a topic going over my findings on what is causing the significant lag in Arcane Odyssey. It was a quickly written post with not a lot of in-depth analysis. Today, I decided to dump Arcane Odyssey once more and try to come to a closer conclusion on what was really causing the lag in Arcane Odyssey–now that we have Occlusion Culling and other miscellaneous optimizations to the Roblox Engine. Fortunately, and unexpected to me, I have been able to come up with a general explanation for why the game suffers these problems.

As always, note before going ahead: all of the information I have collected here come from a single MicroProfiler dump and should not be used as a complete source of evidence for any problems with Arcane Odyssey’s performance. It is important to test on multiple times and on multiple devices.

Moving along,

Findings

I launched Arcane Odyssey and took a MicroProfiler dump within a minute of joining. An upload of the dump is attached here: microprofile-20250115-185052.html - Google Drive

MicroProfiler dumps can be complex, so let me break this down:


Here I am zoomed in on the frame with the highest render wall time, and straight away, we can see many different threads (some happening parallel). We especially see that the one on the bottom seems extremely long and consists of two parts: prepare and perform. Ideally, we would want our prepare time to be as small as possible, but why is that? That is because the prepare thread yields EVERYTHING from continuing until it is over, which is not ideal to have high if we want good FPS. Many Roblox games do not face this prepare time issue, but Arcane Odyssey does. Here is an annotated version of the screenshot for better understanding (zoom in if you cannot read it well), along with the MicroProfiler CPU FlameGraph and Memory allocations by size FlameGraph:
Annotated Capture

CPU FlameGraph

Memory FlameGraph (by allocation size)

Now what can we do to reduce the amount of time it takes to prepare the rendering process, well …

Solutions

1. Reduce usage of unnecessary humanoids, and remove the humanoid instance from non-humanoid objects/entities.

When taking a look at the Capture and CPU FlameGraph, much of prepare is taken up by a tag called updateInvalidatedFastClusters. According to the Roblox MicroProfiler Tag reference, this is for preparing FastClusters which are usually used to render Humanoids.
image
If Arcane Odyssey can reduce the amount of Humanoids, especially under non-humanoid objects, we might be able to significantly improve the speed of Perform.

2. Reduce mesh and material variation.

Another major tag is the updateInstancedClusters. Again, according to the tag reference, this prepares static geometry that use instanced rendering–like parts or meshes. We can reduce the amount of instanced clusters by checking if same meshes share the same ID – then grouping them if not – , getting rid of unneeded/redundant materials and meshes, and grouping together certain sets of materials/meshes if possible.

My take

I believe a lot of this will be solved (at the least updateInvalidatedFastClusters could be reduced a bit) when occluded characters begin to be culled, along with other types:


We have to wait and see until it is added to occlusion culling, but ultimately, a lot of this is up to Vetex to fix. I am not a developer for the game, so many things I cannot speak on how to tackle.

Closing remarks

I will be keeping this topic/thread updated throughout this year, so be sure to come back, there will possibly be new information! If anyone else knows anything, feel free to let me know–there is a lot to dissect, and I cannot catch everything. And remember, feel free to ask any questions, as I will try to the best of my ability to answer them!

Device Specifications

These are my specs I used to conduct these tests:

CPU: AMD Ryzen 5 5600X 6-Core @ 3.70 GHz
GPU: Nvidia RTX 3070
RAM: 32.0 GB DDR4
OS: Windows 11 Pro 24H2 10.0.261000 Build 26100
22 Likes

When nobody got you, Ramdoys got you
Can I get some "Amen"s in chat?

4 Likes

i wonder if vet will see this

1 Like

My goodness, you madman.

1 Like

I wonder if vetex would mind if someone pinged him in this topic
(no, im not willing to find it out myself)

1 Like

@Vetex

10 Likes

You did it. You crazy son of a bitch, you did it.

1 Like

Thank you jup I was going to do it if he didn’t answer until tomorrow morning

1 Like

Do you know of any objects in AO like this?

1 Like

Without access to the game, it is impossible for me to know what non-humanoid objects have a humanoid instance parented under them. It will require confirmation from Vetex, if there are any, on what they are.

Do you think a lot of it just comes down to the crew the player spawns with on their ship? Or the NPCs in the cities they spawned near in Nimbus or back when the spawn was Ravenna?

1 Like

In the case of humanoids, any more humanoids will cause more fast clusters to be made and delegated, so any more humanoids will always be bad on performance. NPCs that are no longer in the client’s DataModel will be moved out of memory, so moving to different places will not cause any issues as far as I know.

Sharks, maybe? They take damage like a player does, so I’d be willing to bet they use Humanoids

1 Like

Is there any way to remedy this while still having the crews and the town npcs, or does it just have to be one or the other?

1 Like

Sharks probably do use humanoids, that is a good one!

One or the other. There are ways you can try to optimize, but they will probably be futile. You will have to wait until more optimizations come into the Roblox Engine directly to notice change without action.

Guess I’d rather live with meh performance then cause I really like the crews and town npcs. If sharks are humanoids I guess the same would go for other sea monsters? Also depending on how much can be done with solution number 2 could it possibly make up for the humanoid stuff, does it just not work like that, or is the humanoid problem that bad?

Vetex did say this in the last optimization post

which if this works as it should I don’t know why humanoids still cause so much lag

It can definitely make up for the humanoid stuff. I do want to make this clear though, there are ways to keep the NPCs but improve performance. AnimationControllers, disabling unused properties, etc… In this scenario, lowering geometry of the humanoids can help too or removing unused instances under these NPCs. I would like to say, simple sea monsters like sharks would not need a humanoid and can be added in with a custom script.

I have no idea how the unloading system works, but I never noticed any sort of substantially performance increase after it was implemented. It is possible the way he made it unload is flawed, like keeping the humanoid somewhere in the DOM which will still make it be apart of fast clusters.