Physics does not replicate to parts locally moved to ReplicatedStorage

Changes to instances are not replicated to clients who have locally moved the part into ReplicatedStorage. This only applies to changes made via Physics (i.e. gravity or Constraints), while changes made via directly setting properties in a script replicate just fine.

Reproduction:

  1. Create a part on the server
  2. On the client, move the part into ReplicatedStorage
  3. Have the part’s network owner perform some kind of physics on the part
  4. On the client, move the part back into Workspace
    You will now find that the part has the wrong properties and won’t be corrected until the part is awoken from a sleeping state.

Demo:
The client is on the top, the server is on the bottom. Here, my “physics” is just letting the part fall due to gravity. The change to Anchored is replicated just fine, but the changes to CFrame and Velocity etc. do not replicate.


physics-repstorage-doesnt-rep.rbxl (56.8 KB)

Expected behavior

To quote the documentation for ReplicatedStorage:

Objects parented to this service are fully replicated to clients and normal replication rules apply.

So, I expect CFrame to be replicated within this container. At the very least, it should get updated after moving it back into Workspace.

For my use case, I am allowing players to block/unblock each other. This entails “hiding” their character and all of their belongings by parenting them to ReplicatedStorage. It is not feasible to track every descendant and update their Transparency & CanCollide & CanQuery & CanTouch & Enabled; this would be obscenely non-performant and still prone to error since the server might update those properties.

For anyone trying to do something similar, I have found a workaround. It’s ridiculous, but it does work.

Instead of parenting to ReplicatedStorage, parent it to a ViewportFrame in workspace. When it’s time to re-show the part, parent it to ReplicatedStorage for 1 frame before putting it back in workspace. For example:

local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local vf = Instance.new("ViewportFrame", workspace)

local target = workspace.Part

local function hide()
  target.Parent = vf
end

local function show()
  target.Parent = ReplicatedStorage
  RunService.RenderStepped:Wait()
  target.Parent = workspace
end

The fact that you have to parent it to ReplicatedStorage for 1 frame is a bug in it of itself, but that’s a report for another day.

So, this is a tricky one.

We technically consider this “expected behavior”, because Client-local changes are not officially supported and may lead to undefined behavior.

It sometimes works, and the use-cases that work we make sure not to break, but moving a part from Workspace to Replicated Storage locally is an example of completely desynchronizing something in a way that we don’t support.

By moving something out of Workspace you essentially re-configure it in a way that makes it difficult to synchronize, especially with respect to Physics updates. Things outside of Workspace are not supposed to simulate, ETC, and therefore don’t receive updates.

Maybe can you update the documentation for ReplicatedStorage so that it doesn’t contain this misleading quote?

Objects parented to this service are fully replicated to clients and normal replication rules apply.

We could probably add a note, let me ask internal documentation folks.

It’s tricky, because technically the description is not incorrect. If the object is in ReplicatedStorage on both Server and Client, there is an implicit understanding that Physics are not simulated/replicated on those objects, but all other replications functionalities work.

I think in your case if you were changing properties, they would still replicate correctly despite the desync, only Physics doesn’t work because of the Workspace requirement.