Procedurally generated obby not replicating consistently for all clients

Hello, I’m RJ and I’m one of the Gameplay Programmers for the Math Obby by BrainySpinach Math. Our obby is currently made up of a few courses, each of which are procedurally generated (and regenerated every 60 minutes).

Our process for doing this is to randomly select an obstacle from our storage folder and add it to the course. Originally, this folder was located in ReplicatedStorage.
When we tested the obby on a live server for the first time, players and admins noticed an issue with the courses after regeneration. (Edit: this does not happen on the first courses that are generated when the server opens). Some of the parts of certain obstacle would not load for some clients, whilst loading for others.

The images below show what I mean by this (collected from a test with 4 players on a local server in Studio):

The hexagons load fine on the top client, but are missing on the bottom client:

The squares load fine on the top client (again), and are (again) mostly missing on the bottom client:

Another example:

The only correlation I have noticed between the parts that aren’t loading is that they are either a MeshPart themselves, or in a model with a MeshPart. I don’t believe this issue has been reported with any of the obstacles using just parts and union operations.

After considering that there may be some inconsistency in client replication of the courses after the server regenerates them, I decided to try moving the folder containing obstacles to ServerStorage, as opposed to keeping it in ReplicatedStorage. There was no reason for these obstacles to be stored in ReplicatedStorage anyway, they were only ever used by the server. I noticed an apparent reduction in the frequency of the issue, however, I cannot confirm whether this was a direct result of the location change – the issue still persisted, even if it appeared, in the few iterations of testing I did, to be less frequent.

The specific process for generating a course is: clone an obstacle from storage, change it’s name and register it’s checkpoints. Parent the obstacle to the new course after changing its position so it is added to the end of the so-far generated course.

We’ve reviewed our code and found no indications of what the problem here could be, and it makes it so much more bizarre that not all clients are experiencing this issue. In the tests I completed on a local server, the first connected client did not experience the issue, but this has not been confirmed in a live server test.

Edit: the screenshots shown are of simultaneously connected clients in a Studio local server test (4 players).

3 Likes

I can’t look into the code myself but I’ll tell you something that I’d try when debugging.

1: Deleting all the meshes, temporally, but do it to check if that’s surely the problem.
2: If the parts aren’t “Loading” , where are they, are they invisible or just completely no existent.
3: Are there any welds or constraints? It could be possible that the meshes are failing to load while attached to other parts, causing them to disappear with the meshes.

4 Likes

There may be a design level error. Something in the structure, flow, or branching logic of your code that does not handle all possible cases the way you intend, and is still correct as far as the interpreter can tell.

It is common, especially with early versions and builds of a project, to have code that runs without interpreter errors or warnings, and that appears to mostly work but does not completely produce the intended results.

Are you absolutely certain that regeneration begins from a totally clean state such that any inputs and data dependencies are in an identical state to what they would be immediately before the very first generation?

Are the missing mesh parts and models Cloned from other instances during regeneration? During regeneration, does your code ensure that the source instances exist at the actual time of Cloning?

Before regeneration, do you :Destroy() a parent folder, or iteratively :Destroy() all parts and models? Are any left behind just before regenerating?

Do the parts and models exist after regeneration but do not have the mesh defined properly in some cases?

For debug purposes, try writing text output to a GUI at each step of the regeneration process, particularly at each step of code directly related to generating these missing parts.

4 Likes

Thank you for the detailed answer.
I don’t believe the issue is due to a regeneration inconsistency in the server code, otherwise surely the problem would be replicated across all clients. The confusion here is, how is it that the course is correctly regenerated for some clients, but not completely there for others.

1 Like

Are you using StreamingEnabled? When either your client RAM usage, client internet connection, or Roblox server side services are under high load or experiencing high latency, some parts may not replicate as consistently, OR can replicate but then disappear from the client (as witnessed in a different experience).

Try adding a LocalScript that watches for :DescendantAdded() on the workspace or the target folder. If descendant instance is a MeshPart or any of the parts you’re looking for (define your criteria) then write some relevant text out to a GUI, or to Chat.

I would also use DescendantRemoved() and write those events to GUI.

Point being you want to check line by line, instance by instance, what is actually happening. Ignoring anything that works consistently and narrowing the search to find specific details that might still matter.

3 Likes

Further checks have shown that the instances still exist in the workspace, and are still able to be collided with. I can confirm that the issue is only seen with MeshParts and UnionOperations (not always within the same model, but in most cases).

I have attempted to change the RenderFidelity of the MeshParts from Performance to Automatic, which made no difference.

I then decided to implement your suggestion anyway:

I added the following LocalScript to StarterPlayerScripts:

workspace.DescendantAdded:Connect(function(newDesc)
	if newDesc:IsA("MeshPart") and newDesc:IsDescendantOf(workspace.Courses) then
		print(newDesc.Name .. " " .. newDesc.Parent.Name .. " " .. newDesc.Parent.Parent.Name)
	end
end)

workspace.DescendantRemoving:Connect(function(desc)
	if desc:IsA("MeshPart") and desc:IsDescendantOf(workspace.Courses) then
		warn(desc.Name .. " " .. desc.Parent.Name .. " " .. desc.Parent.Parent.Name)
	end
end)

Whilst no unexpected addition or removal of instances was seen (as already established at the top of this reply), after adding this script, I’ve been unable to replicate the issue again. I really don’t understand why this is, but there must be some underlying issue which I’m still struggling to identify.

I’ve tested course regeneration around 30-40 times with this new LocalScript in, and observed no issues, but I’m going to continue to monitor the issue and experiment with this script to see if anything changes.

Edit: After a few more rounds of testing, the issue has occurred again, so this LocalScript is, in fact, not magic.

1 Like

Please see the above reply.

No. I’ve now confirmed that this is only happening to MeshParts and UnionOperations, not always in the same models.

@petro180 Hello, tagging you on this topic to see if you ever learnt anything from this post, as it came up in my research and might be related somewhat. (This is one of the only other issues I have found so far that resembles what I am experiencing).

Additional related posts from 2018:

One reply in the Unions thread suggesting preload using ContentProvider or else client should just rejoin the game:

So possibly a long standing client behaviour if the device connection is slow to load replicated instances locally?

If all else fails, maybe use the DescendantAdded code to test on the client whether the MeshID and TextureID are set, then optionally reassign the correct value to each property. Might require a remote event or remote function depending on how you want to implement the check and rebuild.

1 Like

I had to look back through the code, and as someone mentioned above, the solution of preloading the assets had solved it. I still have the code in the game to this day so I’m not sure if it was ever truly fixed or not.

1 Like