Physics doesn't replicate from the client to the server when all parts are massless and the client adds a non-massless part to the assembly

Hello there!

I recently noticed a bug when working on my game. Say you have an assembly on the server with all the parts having the Massless property enabled. When the assembly’s network owner gets set to the client and the client creates a new part & adds it to the assembly without setting it to Massless too then the physics no longer gets replicated to the server.

The video below demonstrates this. A quick explanation of what’s going on in the video is that the two parts are exact copies of each other, with the only difference (other than the color) is that the code in blue one adds a little white part on the client to the assembly, which gets set to Massless, but the code in the red one doesn’t set it to Massless.

Reproduction

Create an unanchored part in workspace and set its Massless property to true.

Then create a server script (I added mine inside of the part) and with it set the network owner of the part to the client. I made mine like this:

local Players = game:GetService("Players")

local primary = script.Parent

-- A little countdown so the client could load in and get ready
for i = 3, 1, -1 do
	print("Setting part network owner in "..i)
	task.wait(1)
end

local player: Player = Players:GetPlayers()[1]
primary:SetNetworkOwner(player)
print("Part network owner set")

print("Firing remote event")
primary.RemoteEvent:FireAllClients()

Next up, create a client script (I added this one inside the part as well with RunContext set to Client). Make it create another part and weld it to the original assembly & set the part to be Massless as well. Then perform some physics-related things with said assembly. The code I made uses AlignPosition, however as far as I’ve tested, the bug still occurs with other physics operations as well, such as :ApplyImpulse. Anyways, here is the code I used:

local Players = game:GetService("Players")
local RunService = game:GetService("RunService")

local player: Player = Players.LocalPlayer
local character: Model = player.Character or player.CharacterAdded:Wait()

local part = script.Parent

print("Client is ready")

-- Create a new part, weld it and make the assembly move with AlignPosition
part:WaitForChild("RemoteEvent").OnClientEvent:Connect(function()
	print("Client received event")

	local clientPart = Instance.new("Part")
	clientPart.Position = part.Position + Vector3.yAxis * 0.5
	clientPart.Size = Vector3.one * 0.5
	clientPart.Material = Enum.Material.Neon
	clientPart.Color = Color3.new(1, 1, 1)
	clientPart.Massless = true -- If this is set to false, physics doesn't replicate. If this is set to true, physics replicates
	clientPart.Parent = part
	
	local weldConstraint = Instance.new("WeldConstraint")
	weldConstraint.Part0 = part
	weldConstraint.Part1 = clientPart
	weldConstraint.Parent = clientPart
	
	local attachment = Instance.new("Attachment")
	attachment.Parent = part
	attachment.WorldPosition = clientPart.Position
	
	local alignPosition = Instance.new("AlignPosition")
	alignPosition.Mode = Enum.PositionAlignmentMode.OneAttachment
	alignPosition.MaxForce = 100000
	alignPosition.Responsiveness = 10
	alignPosition.Attachment0 = attachment
	alignPosition.Parent = part
	
	RunService.Heartbeat:Connect(function() -- Every frame, set the position of the AlignPosition a little above the character
		alignPosition.Position = character:GetPivot().Position + Vector3.new(0, 5, 0)
	end)
end)

(The way I made it also requires a remote event inside the part that the server fires and the client receives)

So in total my set up looks like this
image

And that’s it.

I’ve already set all of this up in a simple place file and created a blue & red part, of which blue replicates and red does not. They are both set up as described above, the only important difference being that the red one has the client-sided part set to non-Massless whereas the the blue one has it set to Massless
Bug test.rbxl (66.0 KB)

System Information

AMD Ryzen 5 5600G with Radeon Graphics
NVIDIA GeForce RTX 3060

Expected behavior

Once the client adds a non-Massless part to the assembly, then the physics shouldn’t stop replicating from the client to the server.

3 Likes

This looks like an AssemblyRootPart desynchronization. Check this property on both the Server and the Client, they’ll probably be different.

This isn’t good, because AssemblyRootParts are what’s used to identify and replicate members of an Assembly. If the Client and Server disagree on what the Assembly Root of a Part is, then they won’t be able replicate any of it’s physics to each other, as they can’t agree on who has Network Ownership of it.

The reason adding a non-Massless Part to an Assembly with only Massless Parts does this, is because Parts with Mass are automatically assigned higher priority to become the Assembly Root.

You cannot explicitly set the root part, but the following factors affect probability from highest to lowest:

1. An Anchored part will always be assigned as the root part.

2. Parts with Massless set to false (default) take precedence.

3. Higher RootPriority values take precedence.

4. Precedence based on the part’s size, with multipliers for parts with specific names.

Interesting. The assembly root parts are indeed different. I’ve managed to find a workaround for my case, but is this not a bug in that case?

They’ve closed bug reports that report (almost exactly in some cases) the same thing you are, creating a Part on the Client that desynchronizes the Assembly Root. I think it’s more of a limitation with the current system, however, I just wanted to give the explanation I’ve gathered after seeing several official responses to this specific issue, I’m not telling you to delete this post or anything.

The explanation provided by @Judgy_Oreo is correct. Although admittedly unintuitive, this is expected behavior in Roblox’s current distributed physics model and so is not a bug.

1 Like

Are there any planned improvements or changes to this system? Or is it not being investigated?

There are no planned changes at the moment.

1 Like