Constraint Network Ownership Help

I’m trying to make a physics-based crane that can be operated via multiple different seats (and hopefully multiple players but I’m not there yet)

Generally, it does what I want to do right now, but RopeConstraints are not replicated from the client to the server. I don’t know how to fix this. Here is an example.

(Watch Build Site - Roblox Studio 2024-05-18 15-35-00 | Streamable)

Here is the script.

local DerrickModule = {}

local PlayerService = game:GetService("Players")
local CollectionService = game:GetService("CollectionService")
local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")

function DerrickModule.WaitForCharacterSeat()--Initiated on server
	for index, seat in CollectionService:GetTagged("DerrickSeat") do
		seat:GetPropertyChangedSignal("Occupant"):Connect(function()
			if seat.Occupant then
				local player = PlayerService:GetPlayerFromCharacter(seat.Occupant.Parent)
				seat:SetNetworkOwner(player)
			else
				seat:SetNetworkOwner()
			end
			print(seat:GetNetworkOwner())
		end)
	end
end

local CharacterSeatedThreads = {}
function DerrickModule.CharacterSeated(player, seatPart)
--This is run from a localscript in a player's character when they sit in any seat.
	if not seatPart then
		for i, v in CharacterSeatedThreads do
			if type(v) == "thread" then
				coroutine.close(v)
				v = nil
			elseif type(v) == "userdata" then
				v:Disconnect()
				v = nil
			else
				warn("Unexpected object in CharacterSeatedThreads, set to nil")
				v = nil
			end
		end
		
		return nil
	end
	if seatPart:GetAttribute("SeatType") then
		local seatBehaviour = {}
		function seatBehaviour.Traverse()
			CharacterSeatedThreads.SteerFloat = seatPart:GetPropertyChangedSignal("SteerFloat"):Connect(function()
				seatPart.CylindricalConstraint.AngularVelocity = seatPart.SteerFloat
			end)
		end
		function seatBehaviour.Elevation()
			CharacterSeatedThreads.RenderStepped = RunService.RenderStepped:Connect(function()
				local rope = seatPart.RopeConstraint
				if UserInputService:IsKeyDown(Enum.KeyCode.W) and UserInputService:IsKeyDown(Enum.KeyCode.S) then
					print("Nada")
				elseif UserInputService:IsKeyDown(Enum.KeyCode.S) then
					if rope.Length > 18 then
						rope.Length = rope.Length - 0.1
					end
				elseif UserInputService:IsKeyDown(Enum.KeyCode.W) then
					if rope.Length < 72 then
						rope.Length = rope.Length + 0.1 
					end
				end
			end)
		end
		function seatBehaviour.Rope()
			CharacterSeatedThreads.RenderStepped = RunService.RenderStepped:Connect(function()
				local rope = seatPart.RopeConstraint
				if UserInputService:IsKeyDown(Enum.KeyCode.E) and UserInputService:IsKeyDown(Enum.KeyCode.Q) then
					print("Nada")
				elseif UserInputService:IsKeyDown(Enum.KeyCode.E) then
					rope.Length = rope.Length - 0.25
				elseif UserInputService:IsKeyDown(Enum.KeyCode.Q) then
					rope.Length = rope.Length + 0.25
				end
			end)
		end
		
		
		seatBehaviour[seatPart:GetAttribute("SeatType")]()
	else
		print("Not a Derrick-type seat")
	end
end


return DerrickModule

I’m unfamiliar with working with client-server relationships. Any help would be appreciated.