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:
Create a part on the server
On the client, move the part into ReplicatedStorage
Have the part’s network owner perform some kind of physics on the part
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.
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.
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.
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.